我应该取消订阅活动吗?

时间:2010-11-13 13:46:43

标签: c# events subscribe unsubscribe

我有3个关于事件的问题:

  1. 我是否应该取消订阅订阅的活动?
  2. 如果我不这样做会怎么样?
  3. 在下面的示例中,您将如何取消订阅订阅的活动?
  4. 我有这样的代码:

    Ctor:目的:用于数据库属性更新

    this.PropertyChanged += (o, e) =>
    {
        switch (e.PropertyName)
        {
            case "FirstName": break;
            case "LastName": break;
        }
    };
    

    和this:目的:用于GUI绑定将模型包装到viewmodels

    ObservableCollection<Period> periods = _lpRepo.GetDailyLessonPlanner(data.DailyDate);
    PeriodListViewModel = new ObservableCollection<PeriodViewModel>();
    
    foreach (Period period in periods)
    {
        PeriodViewModel periodViewModel = new PeriodViewModel(period,_lpRepo);
        foreach (DocumentListViewModel documentListViewModel in periodViewModel.DocumentViewModelList)
        {
            documentListViewModel.DeleteDocumentDelegate += new Action<List<Document>>(OnDeleteDocument);
            documentListViewModel.AddDocumentDelegate += new Action(OnAddDocument);
            documentListViewModel.OpenDocumentDelegate += new Action<int, string>(OnOpenDocument);
        }
        PeriodListViewModel.Add(periodViewModel);
    }
    

5 个答案:

答案 0 :(得分:52)

好吧,让我们先回答最后一个问题。您无法可靠地取消订阅您直接使用lambda表达式订阅的事件。你要么需要保持变量周围的委托(所以你仍然可以使用lambda表达式)你需要使用方法组转换。

现在至于你是否真的需要取消订阅,这取决于事件生产者和事件消费者之间的关系。如果事件生成器的活动时间比事件使用者长,那么取消订阅 - 否则生产者将引用消费者,使其保持活动的时间超过应有的时间。只要生产者生成它,事件处理程序也将继续被调用。

现在在许多情况下这不是问题 - 例如,在一个表单中,引发Click事件的按钮可能存在的时间与创建它的表单一样长,其中处理程序通常是订阅...所以没有必要取消订阅。这对于GUI来说非常典型。

同样,如果您仅为单个异步请求创建WebClient,请订阅相关事件并启动异步请求,那么WebClient本身将有资格进行垃圾回收。请求已经完成(假设您没有在别处保留引用)。

基本上,您应该始终考虑生产者和消费者之间的关系。如果生产者的寿命超过了你希望消费者的时间,它会在你不再对它感兴趣之后继续提升事件,那么你应该取消订阅。

答案 1 :(得分:27)

1)这取决于。通常这是一个好主意,但有一些典型的情况你不需要。基本上,如果您确定订阅对象将比事件源更长,那么您应该取消订阅,否则会产生不必要的引用。

但是,如果您的对象订阅了自己的事件,请执行以下操作:

<Window Loaded="self_Loaded" ...>...</Window>

- 然后你不必。

2)订阅事件会对订阅对象进行额外引用。因此,如果您不取消订阅,您的对象可能会通过此引用保持活动,从而有效地进行内存泄漏。通过取消订阅,您将删除该引用。请注意,在自我订阅的情况下,不会出现问题。

3)你可以这样做:

this.PropertyChanged += PropertyChangedHandler;
...
this.PropertyChanged -= PropertyChangedHandler;

,其中

void PropertyChangedHandler(object o, PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "FirstName": break;
        case "LastName": break;
    }
}

答案 2 :(得分:4)

您可以在MSDN上查看this article。引用:

  

阻止您的事件处理程序   在事件发生时被调用   提出,只是取消订阅   事件。为了防止资源   泄漏,取消订阅很重要   处理之前的事件   订户对象。直到你   取消订阅活动,   作为基础的多播委托   发布对象中的事件有一个   参考代表那个   封装订户的事件   处理程序。只要出版   对象持有该引用,您的   订阅者对象不会是垃圾   收集。

答案 3 :(得分:4)

当订阅的实例与正在订阅的实例具有相同的范围时,您不必从事件中取消订阅。

让我们说你是一个表格,你正在订阅一个控件,这两个一起形成一个组。但是,如果您有一个管理表单的中心类,并且您已订阅该表单的Closed事件,则这些事件不会组成一个组,并且您必须在表单关闭后进行取消订阅。

订阅事件会使订阅的实例创建对正在订阅的实例的引用。这可以防止垃圾回收。因此,当您有一个管理表单实例的中心类时,这将使所有表单保留在内存中。

WPF是一个例外,因为它有一个弱事件模型,其中事件使用弱引用进行订阅,并且它不会将表单保存在内存中。但是,当您不是表格的一部分时,最佳做法是取消订阅。

答案 4 :(得分:1)

1。)我是否应该总是取消订阅订阅的活动?
通常是的。唯一的例外是当你订阅的对象不再被引用时,很快就会被垃圾收集。

2。)如果我不这样做会怎么样?
您订阅的对象将保存对委托的引用,该委托又保留对其this指针的引用,因此您将获得内存泄漏。
或者如果处理程序是lamda,它将保留它绑定的任何局部变量,因此也不会被收集。