Gli ascoltatori di eventi dovrebbero essere tenuti in riferimenti deboli?


9

Di solito i listener di eventi non devono sopravvivere agli oggetti che li hanno registrati.

Significa che i listener di eventi dovrebbero essere mantenuti per impostazione predefinita da riferimenti deboli (archiviati in raccolte deboli dai listener di oggetti su cui sono registrati)?

Ci sono casi validi in cui l'ascoltatore dovrebbe sopravvivere al suo creatore?

O forse una situazione del genere è un errore e non dovrebbe essere permesso?


I riferimenti deboli sono generalmente rappresentati da istanze e queste istanze possono anche accumularsi al punto in cui devono essere raccolte come immondizia. Quindi non è un pranzo gratuito. La stessa logica che cancella i riferimenti deboli potrebbe cancellare i riferimenti forti.
Frank Hileman,

Risposte:


7

Perché i listener di eventi non dovrebbero sopravvivere all'oggetto che li ha registrati? Sembra che tu stia supponendo che i listener di eventi debbano essere registrati con metodi di controllo (se prendiamo l'esempio della GUI) - o più precisamente, metodi di oggetti di classi che ereditano i controlli del toolkit della GUI. Non è una necessità: ad esempio, è possibile utilizzare un oggetto specializzato per registrare i listener di eventi e successivamente abbandonare tale oggetto.

Inoltre, se i listener di eventi fossero debolmente riferiti, dovresti effettivamente conservare dei riferimenti anche se non li usi mai. In caso contrario, l'ascoltatore verrà raccolto in modo casuale. Quindi, abbiamo un bug che è

  • Facile da creare per errore (tutto quello che devi fare è dimenticare di memorizzare un oggetto in una variabile di riferimento che non utilizzerai mai).
  • Difficile da notare (otterrai quel bug solo se il GC raccoglie quell'oggetto).
  • Difficile da eseguire il debug (nella sessione di debug - che funziona sempre come una sessione di rilascio - incontrerai quel bug solo se il GC ha raccolto l'oggetto).

E se evitare quel bug non è un incentivo sufficiente, eccone alcuni:

  1. Dovrai pensare a un nome per ogni ascoltatore che crei.

  2. Alcune lingue utilizzano un'analisi statica che genererà un avviso se si dispone di un campo membro privato che non viene mai scritto o mai letto. Dovrai usare un meccanismo per sovrascriverlo.

  3. L'ascoltatore di eventi fa qualcosa e una volta che l'oggetto che ha il suo forte riferimento viene raccolto, smette di farlo. Ora hai qualcosa che influenza lo stato del programma e dipende dal GC, il che significa che il GC influenza lo stato concreto del programma. E questo è MALE !

  4. La gestione dei riferimenti deboli è più lenta, poiché si dispone di un altro livello di riferimento indiretto e poiché è necessario verificare se il riferimento è stato raccolto. Questo non sarebbe un problema se fosse necessario avere listener di eventi in riferimenti deboli, ma non lo è!


5

In generale, sì, dovrebbero essere usati riferimenti deboli. Ma prima dobbiamo essere chiari su cosa intendi per "ascoltatori di eventi".

callback

In alcuni stili di programmazione, specialmente nel contesto di operazioni asincrone, è comune rappresentare una parte di un calcolo come un callback che viene eseguito su un determinato evento. Ad esempio un Promise[ 1 ] può avere un thenmetodo che registra un callback al completamento del passaggio precedente:

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

Qui, i callback registrati da thendevono essere mantenuti da forti riferimenti, poiché la promessa (la fonte dell'evento) è l'unico oggetto che contiene un riferimento al callback. Questo non è un problema in quanto la promessa stessa ha una durata limitata e sarà raccolta rifiuti dopo il completamento della catena di promesse.

Modello di osservatore

Nel modello di osservatore, un soggetto ha un elenco di osservatori dipendenti. Quando il soggetto entra in uno stato, gli osservatori vengono avvisati secondo una certa interfaccia. Gli osservatori possono essere aggiunti e rimossi dall'argomento. Questi osservatori non esistono in un vuoto semantico, ma stanno aspettando eventi per qualche scopo.

Se questo scopo non esiste più, gli osservatori dovrebbero essere rimossi dall'argomento. Anche nelle lingue raccolte con immondizia, questa rimozione potrebbe dover essere eseguita manualmente. Se non riusciamo a rimuovere un osservatore, sarà mantenuto in vita tramite il riferimento dal soggetto all'osservatore, e con esso tutti gli oggetti a cui l'osservatore fa riferimento. Ciò spreca memoria e peggiora le prestazioni poiché l'osservatore (ora inutile) verrà comunque informato.

Riferimenti deboli risolvono questa perdita di memoria, in quanto consentono all'osservatore di essere spazzato via. Quando il soggetto va in giro per avvisare tutti gli osservatori e scopre che uno dei riferimenti deboli a un osservatore è vuoto, quel riferimento può essere rimosso in modo sicuro. In alternativa, i riferimenti deboli potrebbero essere implementati in modo da consentire al soggetto di registrare un callback di cleanup che rimuoverà l'osservatore al momento della raccolta.

Ma nota che i riferimenti deboli sono solo un cerotto che limita il danno dimenticando di rimuovere un osservatore. La soluzione corretta sarebbe quella di assicurarsi che un osservatore venga rimosso quando non è più necessario. Le opzioni includono:

  • Farlo manualmente, ma è soggetto a errori.

  • Usando qualcosa di simile per provare con risorse in Java o usingin C #.

  • Distruzione deterministica, come attraverso il linguaggio RAII. Si noti che in un linguaggio con garbage collection deterministica, ciò potrebbe richiedere riferimenti deboli dal soggetto all'osservatore per attivare il distruttore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.