在哪些情况下需要从事件中分离?

16

我不确定我是否完全理解了在对象中附加事件的含义。

这是我的当前理解,正确或详细说明:

1. 附加本地类事件不需要分离

例子:

this.Closing += new System.ComponentModel.CancelEventHandler(MainWindow_Closing);

public event EventHandler OnMyCustomEvent = delegate { };

我认为,当您的对象被处理或垃圾收集时,函数将被取消分配并自动从事件中分离。

2. 附加到不再需要的对象(= null;)必须从中分离

例子: 附加到计时器的Elapsed事件,您只响应一次。我想您需要将计时器存储在本地变量中,以便在事件触发后可以分离Elapsed事件。因此,在本地方法范围内声明计时器会导致泄漏:

System.Timers.Timer myDataTimer = new System.Timers.Timer(1000); myDataTimer.Elapsed += new System.Timers.ElapsedEventHandler(myDataTimer_Elapsed);

3. 在本地对象中附加事件不需要清除?

例如,如果您创建、监视并让ObservableCollection死亡。如果您使用本地私有函数附加到CollectionChanged事件,那么在垃圾收集您的类时,此函数不会被释放,从而导致ObservableCollection也被释放?

我确定我有一些地方停止使用对象并未分离事件(例如我所做的计时器示例),因此我正在寻找更清晰的解释以了解其工作原理。

3个回答

24

我认为你把它想得比必要的复杂了。你只需要记住两件事:

  • 当你订阅事件时,事件的“所有者”(发布者)通常会保留对你所订阅委托的引用。
  • 如果你使用一个实例方法作为委托的操作,则该委托有一个对其“目标”对象的引用。

这意味着如果你写成:

publisher.SomeEvent += subscriber.SomeMethod;

如果您稍后取消订阅,subscriberpublisher 之前将不符合垃圾回收的条件。

请注意,在许多情况下,subscriber 就是 this

publisher.SomeEvent += myDataTimer_Elapsed;

等同于:

publisher.SomeEvent += this.myDataTimer_Elapsed;

假设这是一个实例方法。

仅仅因为订阅事件并不会导致反向关系,也就是说,订阅者不能让发布者保持活动状态。

顺便说一下,详细信息请参见我的有关事件和委托的文章


1
你说得很对,我把事情复杂化了。在寻找示例时,几乎所有示例都显示从事件中分离,这只会让我相信订阅者可以保持发布者的活动状态。 - Will Eddins
愚蠢的问题:这是否意味着如果发布者订阅了订阅者上的事件,那么两者都不会被收集? - Steven Evers
4
不,这意味着存在循环引用——只要两者没有其他根引用,它们就都有资格进行垃圾回收。 - Jon Skeet

3
剩余的阻止垃圾回收的引用还有一个影响,可能很明显但仍未在该线程中说明:附加的事件处理程序也将被执行。
我经历过几次这种情况。其中一次是我们有一个应用程序,随着运行时间的增长变得越来越慢。应用程序通过加载用户控件以动态方式创建用户界面。容器使用户控件订阅环境中的某些事件,其中之一在控件“卸载”时没有取消订阅。
过了一段时间,每次引发特定事件时都会执行大量事件侦听器。当许多“休眠”实例突然醒来并尝试在同一输入上操作时,这当然可能导致严重的竞争条件。
简而言之,如果您编写代码来挂接事件侦听器,请确保在不再需要时立即释放。我敢肯定,这至少会在未来的某个时候为您节省一次头痛。

1

你需要取消订阅事件的相关情况如下:

public class A
{
    // ...
    public event EventHandler SomethingHappened;
}

public class B
{
    private void DoSomething() { /* ... */ } // instance method

    private void Attach(A obj)
    {
       obj.SomethingHappened += DoSomething();
    }
}

在这种情况下,当您处理B时,obj的事件处理程序仍将存在对其的悬空引用。如果您想回收B的内存,那么您需要先从相关事件处理程序中分离B.DoSomething()
当然,如果事件订阅行像这样,您也可能遇到同样的问题:
obj.SomethingHappened += someOtherObject.Whatever.DoSomething();

现在它是someOtherObject被钩住了,因此无法进行垃圾回收。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接