Czy nasłuchiwanie zdarzeń powinno odbywać się w słabych referencjach?

9

Zwykle detektory zdarzeń nie powinny przeżyć obiektu, który je zarejestrował.

Czy to oznacza, że ​​detektory zdarzeń powinny być domyślnie przechwytywane przez słabe referencje (przechowywane w słabych kolekcjach przez obiekty, w których nasłuchują rejestratorzy)?

Czy istnieją uzasadnione przypadki, w których słuchacz powinien przeżyć swojego twórcę?

A może taka sytuacja jest błędem i nie powinna być dozwolona?

mrpyo
źródło
Słabe referencje są zwykle reprezentowane przez instancje, a instancje te mogą się także kumulować do momentu, w którym muszą zostać zebrane jako śmieci. Więc to nie jest darmowy lunch. Ta sama logika, która usuwa słabe odniesienia, może usunąć silne odniesienia.
Frank Hileman,

Odpowiedzi:

7

Dlaczego detektory zdarzeń nie powinny przeżyć obiektu, który je zarejestrował? Wygląda na to, że zakładasz, że detektory zdarzeń powinny być rejestrowane za pomocą metod kontroli (jeśli weźmiemy przykład GUI) - a ściślej metod przez obiekty klas, które dziedziczą kontrolę zestawu narzędzi GUI. Nie jest to konieczne - możesz na przykład użyć specjalnego obiektu do zarejestrowania detektorów zdarzeń, a następnie porzucić ten obiekt.

Ponadto, jeśli nasłuchiwania zdarzeń były słabo odesłane, musiałbyś faktycznie przechowywać do nich odniesienia, nawet jeśli nigdy ich nie użyjesz. Niezastosowanie się do tego spowoduje, że słuchacz zostanie zebrany w przypadkowym czasie. Mamy więc błąd

  • Łatwe do utworzenia przez pomyłkę (wszystko, co musisz zrobić, to zapomnieć o przechowywaniu obiektu w zmiennej referencyjnej, której nigdy nie użyjesz).
  • Trudno zauważyć (ten błąd dostaniesz tylko wtedy, gdy GC zbierze ten obiekt).
  • Trudne do debugowania (w sesji debugowania - która zawsze działa jak sesja wydania - napotkasz ten błąd tylko wtedy, gdy GC zgromadzi obiekt).

A jeśli uniknięcie tego błędu nie jest wystarczającą zachętą, oto kilka innych:

  1. Będziesz musiał wymyślić nazwę dla każdego utworzonego słuchacza.

  2. Niektóre języki używają analizy statycznej, która generuje ostrzeżenie, jeśli masz prywatne pole członka, które nigdy nie zostanie zapisane lub nigdy nie zostanie przeczytane. Będziesz musiał użyć mechanizmu zastępującego to.

  3. Detektor zdarzeń coś robi, a gdy obiekt o silnym odwołaniu zostanie zebrany, przestanie to robić. Masz teraz coś, co wpływa na stan programu i zależy od GC - co oznacza, że ​​GC wpływa na konkretny stan programu. I to jest ZŁE !

  4. Obsługa słabych referencji jest wolniejsza, ponieważ masz inny poziom pośredni i musisz sprawdzić, czy referencje zostały zebrane. Nie stanowiłoby to problemu, gdyby posiadanie detektorów zdarzeń w słabych referencjach było konieczne - ale tak nie jest!

Idan Arye
źródło
5

Zasadniczo tak, należy stosować słabe odniesienia. Ale najpierw musimy wyjaśnić, co rozumiesz przez „słuchaczy wydarzeń”.

Callbacki

W niektórych stylach programowania, szczególnie w kontekście operacji asynchronicznych, część obliczeń jest często reprezentowana jako wywołanie zwrotne, które jest wykonywane w przypadku określonego zdarzenia. Na przykład Promise[ 1 ] może mieć thenmetodę, która rejestruje wywołanie zwrotne po zakończeniu poprzedniego kroku:

promise =
    Promise.new(async_task)                # - kick off a task
    .then(value => operation_on(value))    # - queue other operations
    .then(value => other_operation(value)) #   that get executed on completion
... # do other stuff in the meanwhile
# later:
result = promise.value # block for the result

W tym przypadku zarejestrowane wywołania zwrotne thenmuszą być utrzymywane przez silne odwołania, ponieważ obietnica (źródło zdarzenia) jest jedynym obiektem zawierającym odwołanie do wywołania zwrotnego. Nie stanowi to problemu, ponieważ sama obietnica ma ograniczony czas życia i będzie zbierana śmieci po zakończeniu łańcucha obietnic.

Wzór obserwatora

We wzorcu obserwatora podmiot ma listę zależnych obserwatorów. Gdy podmiot wchodzi w jakiś stan, obserwatorzy są powiadamiani zgodnie z jakimś interfejsem. Obserwatorów można dodawać i usuwać z tematu. Obserwatorzy ci nie istnieją w próżni semantycznej, ale z jakiegoś powodu czekają na wydarzenia.

Jeśli ten cel już nie istnieje, obserwatorzy powinni zostać usunięci z tematu. Nawet w językach, w których śmieci są gromadzone, usuwanie może wymagać ręcznego wykonania. Jeśli nie usuniemy obserwatora, zostanie on utrzymany przy życiu poprzez odniesienie od podmiotu do obserwatora, a wraz z nim wszystkie obiekty, do których odwołuje się obserwator. To marnuje pamięć i obniża wydajność, ponieważ (teraz bezużyteczny) obserwator będzie nadal powiadamiany.

Słabe referencje naprawiają ten wyciek pamięci, ponieważ pozwalają obserwatorowi na zbieranie śmieci. Gdy podmiot podchodzi, aby powiadomić wszystkich obserwatorów, i stwierdza, że ​​jedno ze słabych odniesień do obserwatora jest puste, odniesienie to można bezpiecznie usunąć. Alternatywnie słabe referencje można zaimplementować w sposób, który pozwala podmiotowi zarejestrować wywołanie zwrotne czyszczenia, które usunie obserwatora po zebraniu.

Ale zauważ, że słabe referencje są jedynie pomocnikiem, który ogranicza szkody, zapominając o usunięciu obserwatora. Prawidłowym rozwiązaniem byłoby upewnienie się, że obserwator zostanie usunięty, gdy nie będzie już potrzebny. Opcje obejmują:

  • Robi to ręcznie, ale jest to podatne na błędy.

  • Używanie czegoś podobnego do try-with-resource w Javie lub usingw C #.

  • Deterministyczne zniszczenie, na przykład poprzez idiom RAII. Zauważ, że w języku z deterministycznym wyrzucaniem elementów bezużytecznych może to nadal wymagać słabych referencji od podmiotu do obserwatora, aby wyzwolić destruktor.

amon
źródło