Gli ascoltatori di eventi dovrebbero essere tenuti in deboli riferimenti?

8

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

Significa che gli ascoltatori di eventi dovrebbero essere tenuti da riferimenti deboli per impostazione predefinita (memorizzati in raccolte deboli da parte degli ascoltatori di oggetti 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?

    
posta mrpyo 11.05.2014 - 23:21
fonte

2 risposte

6

Perché gli ascoltatori di eventi non dovrebbero sopravvivere all'oggetto che li ha registrati? Sembra che si stia presupponendo che gli ascoltatori di eventi debbano essere registrati con metodi di controllo (se prendiamo l'esempio della GUI) - o più precisamente, metodi per oggetti di classi che ereditano i controlli del toolkit della GUI. Questa non è una necessità: potresti, ad esempio, utilizzare un oggetto specializzato per registrare i listener di eventi e troncare quell'oggetto in seguito.

Inoltre, se gli ascoltatori di eventi fossero deferiti, dovresti effettivamente mantenere dei riferimenti a loro anche se non usi mai quel riferimento. In caso contrario, l'ascoltatore verrà raccolto in un momento casuale. Quindi, otteniamo un bug che è

  • Facile da creare per errore (tutto ciò che devi fare è dimenticare di memorizzare un oggetto in una variabile di riferimento che non utilizzerai mai).
  • Difficile da notare (riceverai quell'errore solo se il GC raccoglierà quell'oggetto).
  • Difficile eseguire il debug (nella sessione di debug, che funziona sempre come una sessione di rilascio, il problema si verifica solo se il GC ha raccolto l'oggetto).

E se evitare che il bug non sia un buon incentivo, eccone altri:

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

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

  3. Il listener di eventi fa qualcosa, e una volta che l'oggetto che ha il suo riferimento strong viene raccolto, smetterà di fare quel qualcosa. Ora hai qualcosa che influenza lo stato del programma e dipende dal GC - il che significa che il GC influisce sullo stato concreto del programma. E questo è BAD !

  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 con riferimenti deboli - ma non lo è!

risposta data 12.05.2014 - 00:37
fonte
5

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

richiamate

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 metodo then che registra una richiamata 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 then devono essere tenuti da riferimenti forti, in quanto 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 dopo la catena di promesse.

Pattern osservatore

Nel modello di osservatore, un soggetto ha una lista di osservatori dipendenti. Quando il soggetto entra in uno stato, gli osservatori vengono avvisati secondo alcune interfacce. Gli osservatori possono essere aggiunti e rimossi dal soggetto. Questi osservatori non esistono in un vuoto semantico, ma sono in attesa di eventi per qualche scopo.

Se questo scopo non esiste più, gli osservatori dovrebbero essere rimossi dal soggetto. Anche nei linguaggi raccolti con garbage, questa rimozione potrebbe dover essere eseguita manualmente. Se non riusciamo a rimuovere un osservatore, sarà tenuto in vita tramite il riferimento dal soggetto all'osservatore e con esso tutti gli oggetti a cui fa riferimento l'osservatore. Questo spreca memoria e degrada le prestazioni man mano che l'osservatore (ora inutile) verrà comunque avvisato.

I riferimenti deboli correggono questa perdita di memoria, in quanto consentono all'osservatore di essere raccolto. Quando il soggetto va in giro per notificare a tutti gli osservatori e scopre che uno dei riferimenti deboli a un osservatore è vuoto, tale riferimento può essere rimosso in modo sicuro. In alternativa, i riferimenti deboli potrebbero essere implementati in un modo che consenta al soggetto di registrare un callback di pulizia 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:

  • Eseguendolo manualmente, ma è soggetto a errori.

  • Usare qualcosa di simile a try-with-resource in Java o using in C #.

  • Distruzione deterministica, ad esempio tramite l'idioma RAII. Si noti che in un linguaggio con garbage collection deterministico, questo potrebbe richiedere ancora riferimenti deboli dall'oggetto all'osservatore per attivare il distruttore.

risposta data 12.05.2014 - 12:48
fonte

Leggi altre domande sui tag