Quando usar referências fracas no .net?

50

Não me deparei pessoalmente com uma situação em que precisei usar o tipo WeakReference no .Net, mas a crença popular parece ser que ele deve ser usado em caches. O Dr. Jon Harrop deu um ótimo caso contra o uso de WeakReferences em caches em sua resposta para esta questão.

Eu também costumava ouvir desenvolvedores do AS3 falarem sobre o uso de referências fracas para economizar memória, mas baseado nas conversas que tive, parece adicionar complexidade sem necessariamente atingir o objetivo pretendido, e o comportamento em tempo de execução é bastante imprevisível. Tanto é assim que muitos simplesmente desistem disso e, em vez disso, gerenciam o uso de memória com mais cuidado / otimizam seu código para consumir menos memória (ou fazer o trade-off de mais ciclos de CPU e menor consumo de memória).

O Dr. Jon Harrop também apontou em sua resposta que as referências fracas do .net não são suaves, e há uma coleção agressiva de referências fracas no gen0. De acordo com o MSDN , as referências longas e fracas fornecem o potencial para recriar um objeto, but the state of the object remains unpredictable. !

Dadas essas características, não consigo pensar em uma situação em que referências fracas sejam úteis, talvez alguém possa me esclarecer?

    
por theburningmonk 31.01.2013 / 00:40
fonte

6 respostas

34

Encontrei aplicações práticas legítimas de referências fracas nos três cenários do mundo real que realmente aconteceram comigo:

Aplicativo 1: manipuladores de eventos

Você é um empreendedor. Sua empresa vende um controle de linhas de faísca para o WPF. As vendas são ótimas, mas os custos de suporte estão acabando com você. Muitos clientes estão reclamando de sobrecargas de CPU e vazamentos de memória quando percorrem telas cheias de linhas de faísca. O problema é que o aplicativo está criando novas linhas de ignição à medida que elas são exibidas, mas a vinculação de dados impede que as antigas sejam coletadas como lixo. O que você faz?

Apresente uma referência fraca entre a vinculação de dados e seu controle, de forma que a vinculação de dados não evite mais que seu controle seja coletado como lixo. Em seguida, adicione um finalizador ao seu controle que destrói a vinculação de dados quando é coletada.

Aplicativo 2: gráficos mutáveis

Você é o próximo John Carmack. Você inventou uma nova representação gráfica baseada em gráficos de superfícies de subdivisão hierárquica que faz os jogos de Tim Sweeney parecerem um Nintendo Wii. Obviamente, eu não vou dizer exatamente como funciona, mas tudo gira em torno desse gráfico mutável onde os vizinhos de um vértice pode ser encontrado em um Dictionary<Vertex, SortedSet<Vertex>> . A topologia do gráfico continua mudando conforme o jogador corre. Há apenas um problema: sua estrutura de dados está eliminando subgráficos inalcançáveis enquanto é executada e você precisa removê-los ou perderá memória. Felizmente você é um gênio, então você sabe que há uma classe de algoritmos projetados especificamente para localizar e coletar subgrafos inacessíveis: coletores de lixo! Você leu a monografia excelente de Richard Jones sobre o assunto , mas deixa você perplexo e preocupado com seu prazo iminente. O que você faz?

Simplesmente substituindo seu Dictionary por uma tabela hash fraca, você pode pegar o GC existente e fazer com que ele colete automaticamente seus subgráficos inacessíveis para você! De volta a folhear anúncios da Ferrari.

Aplicativo 3: Decorando árvores

Você está pendurado no teto de uma sala cíclica em um teclado. Você tem 60 segundos para analisar alguns BIG DATA antes que alguém encontre você. Você veio preparado com um analisador baseado em fluxo que depende do GC para coletar fragmentos de AST depois de terem sido analisados. Mas você percebe que precisa de metadados extras em cada AST Node e precisa disso rapidamente. O que você faz?

Você pode usar um Dictionary<Node, Metadata> para associar metadados a cada nó, mas, a menos que você o limpe, as referências strongs do dicionário aos nós AST antigos os manterão vivos e vazarão memória. A solução é uma tabela hash fraca que mantém apenas referências fracas a chaves e o lixo coleta ligações de valor-chave quando a chave fica inacessível. Em seguida, à medida que os nós AST se tornam inacessíveis, eles são coletados como lixo e sua ligação de valor-chave é removida do dicionário, deixando os metadados correspondentes inacessíveis para que também sejam coletados. Então, tudo o que você precisa fazer depois que seu loop principal é terminado é deslizar de volta pela ventilação, lembrando-se de substituí-lo assim que o segurança entrar.

Note que em todos esses três aplicativos do mundo real que realmente aconteceram comigo eu queria que o GC coletasse da forma mais agressiva possível. É por isso que essas são aplicações legítimas. Todo mundo está errado.

    
por 08.02.2013 / 02:10
fonte
18

Given these characteristics, I can't think of a situation where weak references would be useful, perhaps someone could enlighten me?

Documento da Microsoft Padrões de evento fraco .

In applications, it is possible that handlers that are attached to event sources will not be destroyed in coordination with the listener object that attached the handler to the source. This situation can lead to memory leaks. Windows Presentation Foundation (WPF) introduces a design pattern that can be used to address this issue, by providing a dedicated manager class for particular events and implementing an interface on listeners for that event. This design pattern is known as the weak event pattern.

...

The weak event pattern is designed to solve this memory leak problem. The weak event pattern can be used whenever a listener needs to register for an event, but the listener does not explicitly know when to unregister. The weak event pattern can also be used whenever the object lifetime of the source exceeds the useful object lifetime of the listener. (In this case, useful is determined by you.) The weak event pattern allows the listener to register for and receive the event without affecting the object lifetime characteristics of the listener in any way. In effect, the implied reference from the source does not determine whether the listener is eligible for garbage collection. The reference is a weak reference, thus the naming of the weak event pattern and the related APIs. The listener can be garbage collected or otherwise destroyed, and the source can continue without retaining noncollectible handler references to a now destroyed object.

    
por 31.01.2013 / 01:06
fonte
11

Deixe-me colocar isso em primeiro lugar e voltar a ele:

A WeakReference is useful when you want to keep tabs on an object, but you DO NOT want your observations to prevent that object from being collected

Então, vamos começar do começo:

- desculpas antecipadamente por qualquer ofensa não intencional, mas vou voltar ao nível "Dick and Jane" por um momento, já que nunca se pode dizer ao público.

Então, quando você tiver um objeto X - vamos especificá-lo como uma instância de class Foo - ele NÃO PODE viver sozinho (principalmente true); Da mesma forma que "Nenhum homem é uma ilha", existem apenas algumas maneiras pelas quais um objeto pode ser promovido para o Islandhood - embora seja chamado de ser uma raiz do GC na linguagem CLR. Ser uma raiz do GC, ou ter uma cadeia estabelecida de conexões / referências a uma raiz do GC, é basicamente o que determina se Foo x = new Foo() recebe ou não o lixo coletado.

Se você não conseguir voltar para alguma raiz da GC por pilha ou pilha, você está efetivamente órfão e provavelmente será marcado / coletado no próximo ciclo.

Neste ponto, vamos ver alguns exemplos horríveis:

Primeiro, nossa Foo :

public class Foo 
{
    private static volatile int _ref = 0;
    public event EventHandler FooEvent;
    public Foo()
    {
        _ref++;
        Console.WriteLine("I am #{0}", _ref);
    }
    ~Foo()
    {
        Console.WriteLine("#{0} dying!", _ref--);
    }
}

Bastante simples - não é thread-safe, então não tente isso, mas mantém uma "contagem de referência" das instâncias ativas e decrementos quando elas são finalizadas.

Agora vamos ver um FooConsumer :

public class NastySingleton
{
    // Static member status is one way to "get promoted" to a GC root...
    private static NastySingleton _instance = new NastySingleton();
    public static NastySingleton Instance { get { return _instance;} }

    // testing out "Hard references"
    private Dictionary<Foo, int> _counter = new Dictionary<Foo,int>();
    // testing out "Weak references"
    private Dictionary<WeakReference, int> _weakCounter = new Dictionary<WeakReference,int>();

    // Creates a strong link to Foo instance
    public void ListenToThisFoo(Foo foo)
    {
        _counter[foo] = 0;
        foo.FooEvent += (o, e) => _counter[foo]++;
    }

    // Creates a weak link to Foo instance
    public void ListenToThisFooWeakly(Foo foo)
    {
        WeakReference fooRef = new WeakReference(foo);
        _weakCounter[fooRef] = 0;
        foo.FooEvent += (o, e) => _weakCounter[fooRef]++;
    }

    private void HandleEvent(object sender, EventArgs args, Foo originalfoo)
    {
        Console.WriteLine("Derp");
    }
}

Então, temos um objeto que já é uma raiz da GC (bem ... para ser específico, ele será enraizado por meio de uma cadeia direto no domínio do aplicativo que executa esse aplicativo, mas isso é outro tópico) que tem dois métodos de latching em uma instância de Foo - vamos testá-lo:

// Our foo
var f = new Foo();

// Create a "hard reference"
NastySingleton.Instance.ListenToThisFoo(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Agora, a partir do acima, você esperaria que o objeto-que-foi-outrora referido por f seja "colecionável"?

Não, porque existe outro objeto que agora contém uma referência a ele - a Dictionary nessa Singleton instância estática.

Ok, vamos tentar a abordagem fraca:

f = new Foo();
NastySingleton.Instance.ListenToThisFooWeakly(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
// This should collect # 2 - you'll see a "#2 dying"
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Agora, quando falamos de nossa referência a- Foo -que-foi-uma vez- f , não há mais referências "rígidas" ao objeto, por isso é colecionável - o WeakReference criado por o ouvinte fraco não impedirá isso.

Bons casos de uso:

  • Manipuladores de eventos (Embora leia isto primeiro: Eventos Fracos em C # )

  • Você tem uma situação em que você causaria uma "referência recursiva" (ou seja, o objeto A se refere ao objeto B, que se refere ao objeto A, também chamado de "Memory Leak") (edit: derp, claro que isso não é verdade)

  • Você quer "transmitir" algo para uma coleção de objetos, mas não quer ser a coisa que os mantém vivos; um List<WeakReference> pode ser mantido facilmente, e até mesmo removido removendo onde ref.Target == null

por 31.01.2013 / 01:40
fonte
3

Como vazamentos lógicos que são realmente difíceis de rastrear enquanto os usuários tendem a notar que rodar o seu software por um longo tempo tende a ter mais e mais memória e ficar mais lento e mais lento até que eles reiniciem? Eu não faço.

Considere o que acontece se, quando o usuário solicitar a remoção do recurso do aplicativo acima, Thing2 não conseguir manipular adequadamente esse evento em:

  1. Ponteiros
  2. Referências strongs
  3. Referências fracas

... e sob qual um desses erros provavelmente seria detectado durante o teste, e qual deles não iria e iria voar sob o radar como um bug furtivo. A propriedade compartilhada é, com mais frequência do que a maioria, uma ideia sem sentido.

    
por 04.01.2016 / 19:25
fonte
1

Um exemplo muito ilustrativo de referências fracas usadas para um bom efeito é a Tabela Condicional , que é usado pelo DLR (entre outros lugares) para anexar "membros" adicionais a objetos.

Você não quer que a tabela mantenha o objeto vivo. Este conceito simplesmente não poderia funcionar sem referências fracas.

Mas meio que me parece que todos os usos para referências fracas vieram muito depois que eles foram adicionados à linguagem, já que as referências fracas fazem parte do .NET desde a versão 1.1. Parece apenas algo que você gostaria de acrescentar, de modo que a falta de destruição determinista não o leve a um canto no que diz respeito aos recursos de linguagem.

    
por 04.01.2016 / 20:13
fonte
-2

Se você tiver a camada de cache implementada com o C #, é muito melhor colocar seus dados no cache como referências fracas, isso pode ajudar a melhorar o desempenho da camada de cache.

Pense que essa abordagem também pode ser aplicada à implementação da sessão. Como a sessão é um objeto de longa vida na maioria das vezes, pode haver algum caso quando você não tem memória para o novo usuário. Nesse caso, será muito melhor excluir algum outro objeto de sessão do usuário e lançar o OutOfMemoryException.

Além disso, se você tiver um objeto grande em sua aplicação (alguma grande tabela de consulta, etc), isso deve ser usado muito raramente e a recriação de tal objeto não é um procedimento muito caro. Então, melhor tê-lo como uma referência de semana para ter uma maneira de liberar sua memória quando você realmente precisa disso.

    
por 31.01.2013 / 01:01
fonte