通过附加行为防止内存泄漏

时间:2008-08-18 00:49:34

标签: .net wpf memory

我在WPF应用程序中创建了一个“附加行为”,它允许我处理Enter按键并移动到下一个控件。我将其称为EnterKeyTraversal.IsEnabled,您可以在我的博客here上看到该代码。

我现在主要担心的是我可能有内存泄漏,因为我正在处理UIElements上的PreviewKeyDown事件,并且从未明确地“取消”该事件。

防止这种泄漏的最佳方法是什么(如果有的话)?我应该保留我正在管理的元素列表,并在Application.Exit事件中取消挂起PreviewKeyDown事件吗?有没有人在自己的WPF应用程序中获得附加行为的成功,并提出了一个优雅的内存管理解决方案?

11 个答案:

答案 0 :(得分:5)

我不同意DannySmurf

某些WPF布局对象可能会阻塞您的内存,并且当您的应用程序没有被垃圾回收时会使您的应用程序变得非常慢。所以我发现单词的选择是正确的,你正在泄漏内存给你不再使用的对象。您希望这些项目是垃圾收集的,但它们不是,因为某处有一个引用(在本例中是来自事件处理程序)。

现在回答真实的问题:)

我建议你阅读WPF Performance article on MSDN

  

不删除对象上的事件处理程序   可以保持对象活着

     

对象传递给的委托   它的事件实际上是一个参考   到那个对象。因此,事件   处理程序可以使对象保持更长时间   超出预期。执行清洁时   已注册的对象   听一个对象的事件,它是   删除该代表至关重要   在释放对象之前。保持   不需要的物体活着增加了   应用程序的内存使用情况。这是   当对象是。时尤其如此   逻辑树或视觉的根   树。

他们建议您查看Weak Event pattern

另一种解决方案是在完成对象后删除事件处理程序。但我知道,对于附加属性,这一点可能并不总是很清楚..

希望这有帮助!

答案 1 :(得分:4)

是的我知道在过去,Memory Leaks是一个完全不同的主题。但是对于托管代码,“内存泄漏”一词的新含义可能更合适......

微软甚至承认这是一次内存泄漏:

  

为什么要实现WeakEvent模式?

     

倾听事件可能会导致   内存泄漏。典型的技术   听取一个事件就是使用   语言特定的语法   将处理程序附加到事件上   资源。例如,在C#中,那个   语法是:source.SomeEvent + = new   SomeEventHandler(一个MyEventHandler)。

     

这种技术创造了强大的力量   从事件源引用到   事件监听器。通常,附加   监听器的事件处理程序导致   听众有一个对象   受物体影响的一生   源头的生命周期(除非   事件处理程序被明确删除)。   但在某些情况下你可能会   想要对象的生命周期   听众只能被控制   其他因素,如是否   目前属于可视树   应用程序,而不是   源头的生命周期。每当   源对象的生命周期超出了   监听器的对象生存期,   正常事件模式导致a   内存泄漏:保留了监听器   活得比预期长。

我们将WPF用于具有大型ToolWindows的客户端应用程序,可以拖放,所有漂亮的东西,以及所有兼容的XBAP ..但我们遇到了一些没有垃圾收集的ToolWindows的问题。这是因为它仍然依赖于事件监听器。现在,当您关闭窗口并关闭应用程序时,这可能不是问题。但是如果你用很多命令创建非常大的ToolWindows,所有这些命令会一遍又一遍地重新评估,人们必须整天使用你的应用程序..我可以告诉你..它真的堵塞了你的记忆和你的应用程序的响应时间..

此外,我发现向我的经理解释我们有内存泄漏要容易得多,而不是向他解释由于某些需要清理的事件而导致某些对象没有被垃圾收集;)

答案 2 :(得分:4)

除了哲学辩论之外,在查看OP的博文时,我没有看到任何泄漏:

ue.PreviewKeyDown += ue_PreviewKeyDown;

ue_PreviewKeyDown的硬引用存储在ue.PreviewKeyDown

ue_PreviewKeyDownSTATIC方法,不能是GCed

没有存储对ue的硬性引用,因此没有任何内容阻止它GCed

那么......泄漏在哪里?

答案 3 :(得分:2)

@Nick是的,附加行为的事情是,根据定义,它们与你正在处理事件的元素不在同一个对象中。

我认为答案在于以某种方式使用WeakReference,但我没有看到任何简单的代码示例向我解释。 :)

答案 4 :(得分:1)

您是否实施了“弱事件模式”而不是常规事件?

  1. Weak Event Pattern in WPF
  2. Weak Event Patterns (MSDN)

答案 5 :(得分:1)

解释我对约翰芬顿的评论帖这里是我的答案。让我们看看下面的例子:

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)
    {
    }
}

如果你有

a.Clicked += b.HandleClicked;

并且只将b设置为null两个引用weakA和weakB保持活着!如果你只设置一个空值b保持活着而不是一个(证明John Fenton说错了,说明硬件引用存储在事件提供者中 - 在这种情况下为a)。

这导致我得出错误的结论

a.Clicked += B.StaticHandleClicked;

会导致泄漏,因为我虽然a的实例将由静态处理程序保存。事实并非如此(测试我的程序)。在静态事件处理程序或事件的情况下,它是相反的方式。如果你写

A.StaticClicked += b.HandleClicked;

将参考b。

答案 6 :(得分:0)

确保事件引用元素与它们引用的对象一起使用,就像表单控件中的文本框一样。或者,如果无法阻止。在全局帮助器类上创建静态事件,然后监视事件的全局帮助器类。如果使用WeakReference无法完成这两个步骤,它们通常适用于这些情况,但它们会带来开销。

答案 7 :(得分:0)

我刚看了你的博客文章,我觉得你有一些误导性的建议,马特。如果此处存在实际内存泄漏,那么这是.NET Framework中的错误,而不是您必须在代码中修复的内容。

我认为你(以及你博客上的海报)实际上在这里讨论的并不是泄漏,而是持续消耗内存。那不是一回事。要清楚,泄漏的内存是由程序保留的内存,然后被放弃(即,指针悬空),并且随后无法释放。由于内存是在.NET中管理的,因此理论上这是不可能的。但是,程序可以保留不断增加的内存量,而不允许对它的引用超出范围(并且有资格进行垃圾收集);但是内存没有泄露。一旦程序退出,GC将把它返回到系统。

因此。要回答你的问题,我认为你实际上没有问题。你肯定没有内存泄漏,从你的代码中,我认为你不必担心,只要内存消耗也是如此。只要您确保不重复分配该事件处理程序而不取消分配它(即,您只需设置一次,或者每次分配它时只删除一次),你似乎在做,你的代码应该没问题。

这似乎是你博客上的海报试图给你的建议,但是他使用了那个惊人的工作“泄漏”,这是一个可怕的词,但许多程序员已经忘记了管理中的真正含义世界;它不适用于此。

答案 8 :(得分:0)

@Arcturus:

  

...堵塞你的记忆,让你的   应用程序真的很慢   不是垃圾收集。

这是非常明显的,我并不反对。但是:

  

......你正在把记忆泄漏给对象   你不再使用...因为   有一个参考。

“内存被分配给一个程序,并且由于程序逻辑缺陷,该程序随后失去了访问它的能力”(维基百科,“内存泄漏”)

如果存在对程序可以访问的对象的活动引用,那么按照定义它不会泄漏内存。泄漏意味着对象不再可访问(对您或OS / Framework),并且在操作系统当前会话的生命周期内不会释放 。这不是这种情况。

(对不起,我是一个语义纳粹...也许我有点老了,但泄漏有一个非常具体的意义。人们倾向于使用“内存泄漏”这些天意味着消耗2KB内存的任何东西他们想......)

但是,当然,如果你没有释放一个事件处理程序,那么在关闭之前垃圾收集器回收你的进程内存时,它所附加的对象将不会被释放。但这种行为是完全可以预料的,与你所暗示的相反。如果您希望回收对象,则需要删除任何可能使引用保持活动状态的内容,包括事件处理程序。

答案 9 :(得分:0)

的确如此,

你当然是对的..但是这个世界上出现了一代新一代的程序员,他们永远不会触及非托管代码,我相信语言定义会一次又一次地重新发明。 WPF中的内存泄漏与C / Cpp不同。

或者对我的经理来说,我把它称为内存泄漏..对我的同事们我称之为性能问题!

参考Matt的问题,这可能是您可能需要解决的性能问题。如果您只使用几个屏幕并将这些屏幕控件设置为单例,则可能根本看不到此问题;)。

答案 10 :(得分:-1)

那个(经理位)我当然可以理解并同情。

但无论微软如何称呼它,我都不认为“新”定义是合适的。它很复杂,因为我们不是生活在100%管理的世界中(即使微软喜欢假装我们这样做,微软本身也不会生活在这样的世界中)。当你说内存泄漏时,你可能意味着程序消耗了太多内存(这是用户的定义),或者在退出之前不会释放托管引用(如此处),或者未正确清理非托管引用up(这将是一个真正的内存泄漏),或者从托管代码调用的非托管代码泄漏内存(另一个真正的泄漏)。

在这种情况下,很明显“内存泄漏”意味着什么,即使我们不精确。但与某些人交谈是非常繁琐的,他们称每次过度消费,或者未能收集内存泄漏;当这些人都是程序员时,他们会感到沮丧,因为他们应该知道更好。我认为,对于具有明确含义的技术术语来说,这很重要。调试非常容易。

反正。不要把它变成一个关于语言的通风童话。只是说...