Wyjaśniłem to zamieszanie na blogu pod adresem https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16 . Postaram się to podsumować tutaj, abyś miał jasny pomysł.
Odniesienie oznacza „Potrzeba”:
Przede wszystkim musisz zrozumieć, że jeśli obiekt A zawiera odniesienie do obiektu B, to będzie to oznaczać, że obiekt A potrzebuje obiektu B do działania, prawda? Więc odśmiecacz nie zbierze obiektu B tak długo, jak długo obiekt A będzie żył w pamięci.
Myślę, że ta część powinna być oczywista dla programisty.
+ = Oznacza, wstawianie odniesienia obiektu po prawej stronie do obiektu po lewej stronie:
Ale zamieszanie pochodzi od operatora C # + =. Ten operator nie mówi jasno programiście, że prawa strona tego operatora w rzeczywistości wstrzykuje odniesienie do obiektu po lewej stronie.
Robiąc to, obiekt A myśli, że potrzebuje przedmiotu B, chociaż z twojej perspektywy obiekt A nie powinien przejmować się tym, czy obiekt B żyje, czy nie. Ponieważ obiekt A uważa, że obiekt B jest potrzebny, obiekt A chroni obiekt B przed wyrzucaniem elementów bezużytecznych, dopóki obiekt A żyje. Ale jeśli nie chcesz, aby ochrona została udzielona obiektowi subskrybenta zdarzenia, możesz powiedzieć, że wystąpił wyciek pamięci.
Możesz uniknąć takiego wycieku, odłączając moduł obsługi zdarzeń.
Jak podjąć decyzję?
Ale istnieje wiele zdarzeń i programów obsługi zdarzeń w całej bazie kodu. Czy to oznacza, że musisz wszędzie odłączać programy obsługi zdarzeń? Odpowiedź brzmi: nie. Gdybyś musiał to zrobić, twój kod źródłowy byłby naprawdę brzydki z gadatliwością.
Możesz raczej postępować zgodnie z prostym schematem blokowym, aby określić, czy odłączająca procedura obsługi zdarzeń jest konieczna, czy nie.
W większości przypadków może się okazać, że obiekt subskrybenta zdarzenia jest tak samo ważny jak obiekt wydawcy zdarzenia i oba mają istnieć w tym samym czasie.
Przykład scenariusza, w którym nie musisz się martwić
Na przykład zdarzenie kliknięcia przycisku w oknie.
W tym przypadku wydawcą zdarzenia jest Button, a subskrybentem zdarzenia jest MainWindow. Stosując ten schemat blokowy, zadaj pytanie, czy okno główne (subskrybent zdarzenia) ma być martwe przed przyciskiem (wydawcą zdarzenia)? Oczywiście nie, prawda? To nawet nie ma sensu. Po co więc martwić się odłączaniem modułu obsługi zdarzeń kliknięcia?
Przykład, gdy odłączenie programu obsługi zdarzeń jest MUSI.
Podam jeden przykład, w którym obiekt subskrybenta powinien być martwy przed obiektem wydawcy. Powiedzmy, że Twoje MainWindow publikuje zdarzenie o nazwie „SomethingHappened” i po kliknięciu przycisku wyświetlasz okno potomne z okna głównego. Okno potomne subskrybuje to zdarzenie okna głównego.
Okno potomne subskrybuje zdarzenie w oknie głównym.
Na podstawie tego kodu możemy jasno zrozumieć, że w głównym oknie znajduje się przycisk. Kliknięcie tego przycisku pokazuje okno potomne. Okno potomne nasłuchuje zdarzenia z okna głównego. Po wykonaniu czynności użytkownik zamyka okno potomne.
Teraz, zgodnie ze schematem blokowym, który podałem, jeśli zadasz pytanie „Czy okno podrzędne (subskrybent zdarzenia) powinno być martwe przed wydawcą zdarzenia (okno główne)? Odpowiedź powinna brzmieć TAK. Zgadza się? Więc odłącz moduł obsługi zdarzenia Zwykle robię to ze zdarzenia Unloaded w oknie.
Praktyczna zasada: jeśli widok (tj. WPF, WinForm, UWP, Xamarin Form itp.) Subskrybuje zdarzenie ViewModel, zawsze pamiętaj o odłączeniu obsługi zdarzeń. Ponieważ ViewModel zwykle trwa dłużej niż widok. Tak więc, jeśli ViewModel nie zostanie zniszczony, każdy widok, który zasubskrybował zdarzenie tego ViewModel, pozostanie w pamięci, co nie jest dobre.
Dowód koncepcji przy użyciu profilera pamięci.
Nie będzie zabawnie, jeśli nie możemy zweryfikować koncepcji za pomocą profilera pamięci. W tym eksperymencie użyłem JetBrain dotMemory profilera.
Najpierw uruchomiłem MainWindow, które wygląda tak:
Następnie zrobiłem migawkę pamięci. Następnie trzykrotnie kliknąłem przycisk . Pojawiły się trzy okna potomne. Zamknąłem wszystkie te okna podrzędne i kliknąłem przycisk Force GC w profilerze dotMemory, aby upewnić się, że wywoływany jest Garbage Collector. Następnie zrobiłem kolejną migawkę pamięci i porównałem ją. Ujrzeć! nasz strach był prawdziwy. Okno potomne nie zostało zebrane przez moduł wyrzucania elementów bezużytecznych nawet po zamknięciu. Co więcej, liczba wycieków obiektu ChildWindow jest również wyświetlana jako „ 3 ” (kliknąłem przycisk 3 razy, aby wyświetlić 3 okna potomne).
W takim razie odłączyłem procedurę obsługi zdarzeń, jak pokazano poniżej.
Następnie wykonałem te same czynności i sprawdziłem profiler pamięci. Tym razem wow! koniec wycieku pamięci.