Pergunta

Eu criei um "comportamento anexado" em meu aplicativo WPF que me permite lidar com o pressionamento da tecla Enter e passar para o próximo controle.Eu chamo isso de EnterKeyTraversal.IsEnabled, e você pode ver o código no meu blog aqui.

Minha principal preocupação agora é que posso ter um vazamento de memória, já que estou manipulando o evento PreviewKeyDown em UIElements e nunca "desengancho" explicitamente o evento.

Qual é a melhor abordagem para evitar esse vazamento (se é que existe um)?Devo manter uma lista dos elementos que estou gerenciando e desengatar o evento PreviewKeyDown no evento Application.Exit?Alguém teve sucesso com comportamentos anexados em seus próprios aplicativos WPF e criou uma solução elegante de gerenciamento de memória?

Foi útil?

Solução

Eu não concordo DannySmurf

Alguns objetos de layout WPF podem obstruir sua memória e tornar seu aplicativo muito lento quando não são coletados como lixo.Então acho que a escolha das palavras está correta, você está vazando memória para objetos que não usa mais.Você espera que os itens sejam coletados como lixo, mas não são, porque há uma referência em algum lugar (nesse caso, em um manipulador de eventos).

Agora, uma resposta real :)

Eu aconselho você a ler isso Artigo sobre desempenho do WPF no MSDN

Não remover os manipuladores de eventos em objetos podem manter os objetos vivos

O delegado que um objeto passa ao seu evento é efetivamente uma referência a esse objeto.Portanto, os manipuladores de eventos podem manter os objetos vivos por mais tempo do que o esperado.

Eles aconselham você a olhar para o Padrão de evento fraco

Outra solução seria remover os manipuladores de eventos quando você terminar de usar um objeto.Mas eu sei que com propriedades anexadas esse ponto nem sempre fica claro.

Espero que isto ajude!

Outras dicas

Deixando de lado o debate filosófico, ao olhar a postagem do blog do OP, não vejo nenhum vazamento aqui:

ue.PreviewKeyDown += ue_PreviewKeyDown;

Uma referência difícil para ue_PreviewKeyDown está armazenado em ue.PreviewKeyDown.

ue_PreviewKeyDown é um STATIC método e não pode ser GCed.

Nenhuma referência difícil a ue está sendo armazenado, então nada está impedindo que ele seja GCed.

Então...Onde está o vazamento?

Sim, eu sei que antigamente os Memory Leaks eram um assunto totalmente diferente.Mas com código gerenciado, um novo significado para o termo Memory Leak pode ser mais apropriado...

A Microsoft até reconhece que é um vazamento de memória:

Por que implementar o padrão WeakEvent?

Listening for events can lead to memory leaks. The typical technique for listening to an event is to use the language-specific syntax that attaches a handler to an event on a source. For instance, in C#, that syntax is: source.SomeEvent += new SomeEventHandler(MyEventHandler).

This technique creates a strong reference from the event source to the event listener. Ordinarily, attaching an event handler for a listener causes the listener to have an object lifetime that influenced by the object lifetime for the source (unless the event handler is explicitly removed). But in certain circumstances you might want the object lifetime of the listener to be controlled only by other factors, such as whether it currently belongs to the visual tree of the application, and not by the lifetime of the source. Whenever the source object lifetime extends beyond the object lifetime of the listener, the normal event pattern leads to a memory leak: the listener is kept alive longer than intended.

Usamos WPF para um aplicativo cliente com grandes ToolWindows que podem ser arrastadas e soltas, todas as coisas interessantes e todas compatíveis com um XBAP.Mas tivemos o mesmo problema com algumas ToolWindows que não foram coletadas como lixo.Isso se devia ao fato de ainda depender de ouvintes de eventos.Agora, isso pode não ser um problema quando você fecha a janela e desliga o aplicativo.Mas se você estiver criando ToolWindows muito grandes com muitos comandos, e todos esses comandos forem reavaliados continuamente, e as pessoas deverão usar seu aplicativo o dia todo.Eu posso te contar..isso realmente obstrui a memória e o tempo de resposta do seu aplicativo.

Além disso, acho muito mais fácil explicar ao meu gerente que temos um vazamento de memória, do que explicar a ele que alguns objetos não são coletados como lixo devido a alguns eventos que precisam de limpeza;)

@Nick Sim, o problema com os comportamentos anexados é que, por definição, eles não estão no mesmo objeto que os elementos cujos eventos você está manipulando.

Acho que a resposta está no uso de WeakReference de alguma forma, mas não vi nenhum exemplo de código simples para me explicar isso.:)

Você já pensou em implementar o "Padrão de Evento Fraco" em vez de eventos regulares?

  1. Padrão de evento fraco no WPF
  2. Padrões de eventos fracos (MSDN)

Para explicar meu comentário na postagem de John Fenton, aqui está minha resposta.Vejamos o seguinte exemplo:

class Program
{
    static void Main(string[] args)
    {
        var a = new A();
        var b = new B();

        a.Clicked += b.HandleClicked;
        //a.Clicked += B.StaticHandleClicked;
        //A.StaticClicked += b.HandleClicked;

        var weakA = new WeakReference(a);
        var weakB = new WeakReference(b);

        a = null;
        //b = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        Console.WriteLine("a is alive: " + weakA.IsAlive);
        Console.WriteLine("b is alive: " + weakB.IsAlive);
        Console.ReadKey();
    }


}

class A
{
    public event EventHandler Clicked;
    public static event EventHandler StaticClicked;
}

class B
{
    public void HandleClicked(object sender, EventArgs e)
    {
    }

    public static void StaticHandleClicked(object sender, EventArgs e)
    {
    }
}

Se você tem

a.Clicked += b.HandleClicked;

e defina apenas b como nulo, ambas as referências fracaA e fracaB permanecem vivas!Se você definir apenas a como nulo, b permanecerá ativo, mas não a (o que prova que John Fenton está errado ao afirmar que uma referência física é armazenada no provedor de eventos - neste caso, a).

Isso me levou à conclusão ERRADA de que

a.Clicked += B.StaticHandleClicked;

levaria a um vazamento porque pensei que a instância de a seria mantida pelo manipulador estático.Este não é o caso (teste meu programa).No caso de manipulador de eventos ou eventos estáticos, é o contrário.Se você escrever

A.StaticClicked += b.HandleClicked;

uma referência será mantida para b.

Certifique-se de que os elementos de referência de eventos estejam no objeto aos quais estão fazendo referência, como caixas de texto no controle de formulário.Ou se isso não puder ser evitado.Crie um evento estático em uma classe auxiliar global e monitore a classe auxiliar global em busca de eventos.Se essas duas etapas não puderem ser executadas, tente usar um WeakReference, elas geralmente são perfeitas para essas situações, mas trazem sobrecarga.

Acabei de ler a postagem do seu blog e acho que você recebeu alguns conselhos enganosos, Matt.Se houver um real memória vazar aqui, então isso é um bug no .NET Framework, e não algo que você possa necessariamente corrigir em seu código.

O que eu acho que você (e o postador do seu blog) estão falando aqui não é um vazamento, mas sim um consumo contínuo de memória.Isso não é a mesma coisa.Para ser claro, memória vazada é a memória reservada por um programa e depois abandonada (ou seja, um ponteiro fica pendurado) e que subsequentemente não pode ser liberada.Como a memória é gerenciada no .NET, isso é teoricamente impossível.É possível, entretanto, que um programa reserve uma quantidade cada vez maior de memória sem permitir que referências a ele saiam do escopo (e se tornem elegíveis para coleta de lixo);no entanto, essa memória não vazou.O GC irá devolvê-lo ao sistema assim que o programa terminar.

Então.Para responder à sua pergunta, não acho que você realmente tenha um problema aqui.Você certamente não tem vazamento de memória e, pelo seu código, não acho que você precise se preocupar, no que diz respeito ao consumo de memória.Contanto que você certifique-se de não atribuir repetidamente esse manipulador de eventos sem nunca desatribuí-lo (ou seja, que você o configure apenas uma vez ou que o remova exatamente uma vez para cada vez que atribuí-lo), o que você parece estar fazendo, seu código deve estar bem.

Parece que esse é o conselho que o postador do seu blog estava tentando lhe dar, mas ele usou aquele alarmante "vazamento" de trabalho, que é uma palavra assustadora, mas da qual muitos programadores esqueceram o real significado no mundo gerenciado;isso não se aplica aqui.

@Arcturus:

...

Isso é extremamente óbvio e não discordo.No entanto:

...you are leaking memory to object that you no longer use... because there is a reference to them.

"a memória é alocada para um programa e esse programa subsequentemente perde a capacidade de acessá-la devido a falhas lógicas do programa" (Wikipedia, "Vazamento de memória")

Se houver uma referência ativa a um objeto que seu programa possa acessar, então por definição não está vazando memória.Um vazamento significa que o objeto não está mais acessível (para você ou para o SO/Framework) e não será liberado durante o tempo de vida da sessão atual do sistema operacional.Este não é o caso aqui.

(Desculpe ser um nazista semântico...talvez eu seja um pouco antiquado, mas vazamento tem um significado muito específico.As pessoas tendem a usar "vazamento de memória" hoje em dia para significar qualquer coisa que consuma 2 KB de memória a mais do que desejam...)

Mas é claro, se você não liberar um manipulador de eventos, o objeto ao qual ele está anexado não será liberado até que a memória do seu processo seja recuperada pelo coletor de lixo no desligamento.Mas esse comportamento é totalmente esperado, ao contrário do que você parece sugerir.Se você espera que um objeto seja recuperado, será necessário remover qualquer coisa que possa manter a referência ativa, incluindo manipuladores de eventos.

Verdade verdade,

Você está certo, claro..Mas há toda uma nova geração de programadores nascendo neste mundo que nunca tocará em código não gerenciado, e acredito que as definições de linguagem irão se reinventar continuamente.Vazamentos de memória no WPF são diferentes do C/Cpp.

Ou, claro, para meus gerentes, me referi a isso como vazamento de memória.aos meus colegas, referi-me a isso como uma questão de desempenho!

Referindo-se ao problema de Matt, pode ser um problema de desempenho que você talvez precise resolver.Se você usar apenas algumas telas e tornar esses controles de tela únicos, talvez você não veja esse problema;).

Bem, isso (a parte do gerente) eu certamente posso entender e simpatizar.

Mas seja lá como a Microsoft a chame, não acho que uma “nova” definição seja apropriada.É complicado, porque não vivemos num mundo 100% gerido (embora a Microsoft goste de fingir que sim, a própria Microsoft não vive num mundo assim).Quando você diz vazamento de memória, pode significar que um programa está consumindo muita memória (essa é a definição do usuário), ou que uma referência gerenciada não será liberada até a saída (como aqui), ou que uma referência não gerenciada não está sendo devidamente limpa up (isso seria um vazamento de memória real) ou que o código não gerenciado chamado do código gerenciado está vazando memória (outro vazamento real).

Neste caso, é óbvio o que significa “vazamento de memória”, embora estejamos sendo imprecisos.Mas é terrivelmente tedioso conversar com algumas pessoas, que consideram todo consumo excessivo ou falha na coleta um vazamento de memória;e é frustrante quando essas pessoas são programadores, que supostamente sabem mais.É importante que os termos técnicos tenham significados inequívocos, eu acho.A depuração é muito mais fácil quando isso acontece.

De qualquer forma.Não pretendo transformar isso em uma discussão sobre linguagem.Apenas dizendo...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top