Os ouvintes de eventos devem ser mantidos em referências fracas?

9

Normalmente, os ouvintes de eventos não devem sobreviver ao objeto que os registrou.

Isso significa que os ouvintes de eventos devem ser mantidos por referências fracas por padrão (armazenadas em coleções fracas pelos objetos em que os ouvintes estão registrados)?

Existem casos válidos em que o ouvinte deve sobreviver ao seu criador?

Ou talvez uma situação como essa seja um erro e não deva ser permitida?

mrpyo
fonte
Referências fracas geralmente são representadas por instâncias, e essas instâncias também podem se acumular até o ponto em que devem ser coletadas como lixo. Portanto, não é um almoço grátis. A mesma lógica que limpa as referências fracas poderia limpar referências fortes.
91117 Frank Hileman

Respostas:

7

Por que os ouvintes de eventos não devem sobreviver ao objeto que os registrou? Parece que você está assumindo que os ouvintes de eventos devem ser registrados por métodos de controle (se usarmos o exemplo da GUI) - ou mais precisamente, métodos por objetos de classes que herdam os controles do kit de ferramentas da GUI. Isso não é necessário - você pode, por exemplo, usar um objeto especializado para registrar ouvintes de eventos e abandonar esse objeto posteriormente.

Além disso, se os ouvintes de evento fossem fracamente referenciados, você teria que realmente manter referências a eles, mesmo que nunca use essa referência. Caso contrário, o ouvinte será coletado aleatoriamente. Então, nós temos um bug que é

  • Fácil de criar por engano (tudo que você precisa fazer é esquecer de armazenar um objeto em uma variável de referência que você nunca usará).
  • Difícil de perceber (você só receberá esse bug se o GC coletar esse objeto).
  • Difícil de depurar (na sessão de depuração - que sempre funciona como uma sessão de lançamento - você encontrará esse bug apenas se o GC coletar o objeto).

E se evitar esse bug não é um incentivo suficiente, aqui estão mais alguns:

  1. Você terá que pensar em um nome para cada ouvinte que criar.

  2. Alguns idiomas usam análises estáticas que geram um aviso se você tiver um campo de membro privado que nunca seja escrito ou lido. Você precisará usar um mecanismo para substituir isso.

  3. O ouvinte de evento faz alguma coisa e, uma vez que o objeto que possui uma referência forte é coletado, ele pára de fazer isso. Agora você tem algo que afeta o estado do programa e depende do GC - o que significa que o GC afeta o estado concreto do programa. E isso é ruim !

  4. O tratamento de referências fracas é mais lento, pois você tem outro nível de indireção e precisa verificar se a referência foi coletada. Isso não seria um problema se fosse necessário ter ouvintes de eventos em referências fracas - mas não é!

Idan Arye
fonte
5

Em geral, sim, referências fracas devem ser usadas. Mas primeiro precisamos esclarecer o que você quer dizer com "ouvintes de eventos".

Retornos de chamada

Em alguns estilos de programação, especialmente no contexto de operações assíncronas, é comum representar uma parte de um cálculo como um retorno de chamada que é executado em um determinado evento. Por exemplo, um Promise[ 1 ] pode ter um thenmétodo que registra um retorno de chamada após a conclusão da etapa anterior:

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

Aqui, os retornos de chamada registrados por thendevem ser mantidos por fortes referências, pois a promessa (a fonte do evento) é o único objeto que mantém uma referência ao retorno de chamada. Isso não é um problema, pois a promessa em si tem uma vida útil limitada e será coletada como lixo após a conclusão da cadeia de promessas.

Padrão do observador

No padrão do observador, um sujeito tem uma lista de observadores dependentes. Quando o sujeito entra em algum estado, os observadores são notificados de acordo com alguma interface. Os observadores podem ser adicionados e removidos do assunto. Esses observadores não existem no vácuo semântico, mas estão aguardando eventos para algum propósito.

Se esse objetivo não existir mais, os observadores devem ser removidos do assunto. Mesmo em idiomas coletados pelo lixo, essa remoção pode ter que ser realizada manualmente. Se não conseguirmos remover um observador, ele será mantido vivo por meio da referência do sujeito ao observador, e com ele todos os objetos que o observador fizer referência. Isso desperdiça memória e prejudica o desempenho, pois o observador (agora inútil) ainda será notificado.

Referências fracas corrigem esse vazamento de memória, pois permitem que o observador seja coletado como lixo. Quando o assunto avisa todos os observadores e descobre que uma das referências fracas a um observador está vazia, essa referência pode ser removida com segurança. Como alternativa, as referências fracas podem ser implementadas de uma maneira que permita ao sujeito registrar um retorno de chamada de limpeza que removerá o observador na coleta.

Mas observe que referências fracas são apenas um curativo que limitam o dano ao esquecer de remover um observador. A solução correta seria garantir que um observador seja removido quando não for mais necessário. As opções incluem:

  • Fazê-lo manualmente, mas isso é propenso a erros.

  • Usando algo semelhante à tentativa de recurso em Java ou usingem C #.

  • Destruição determinística, como através do idioma RAII. Observe que em um idioma com coleta de lixo determinística, isso ainda pode exigir referências fracas do sujeito para o observador, a fim de acionar o destruidor.

amon
fonte