2010-11-13 77 views
27

我有3个问题,关于事件:我应该取消订阅活动吗?

  1. 我就应该总是退订事件进行订阅?
  2. 如果我没有,会发生什么?
  3. 在以下示例中,您将如何取消订阅订阅事件?

我有例如下面的代码:

构造函数:用途:用于数据库属性更新

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

这:用途:用于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); 
} 
+0

http:// stackoverflow。com/questions/1061727/is-it-bad-to-not-unregister-event-handlinglers – SwDevMan81 2010-11-13 19:00:01

回答

23

1)这取决于。通常这是一个好主意,但有一些典型情况你不需要。基本上,如果您确定订阅对象将超过事件源,则应该取消订阅,否则这会造成不必要的参考。

如果您但是对象订阅它自己的事件,如以下:

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

--then你不必。

2)订阅一个事件会对订阅对象产生额外的引用。所以如果你不取消订阅,你的对象可能会被这个引用保持活着,从而有效地发生内存泄漏。通过取消订阅,您将删除该参考。请注意,在自订的情况下,问题不会出现。

3)你可以做这样的:

​​

其中

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

当我使用你的底层代码时我无法编译?我把一个“;”在最后一个“}”的后面,但它没有编译?我应该在哪里取消订阅您的建议代码? – Elisabeth 2010-11-13 17:49:29

+0

@Lisa:(1)你得到的错误信息是什么? (2)当你不再需要这些事件时,你应该取消订阅。通常这是对象生命周期的结束。如果您可以更多地了解要订阅事件的对象,则可以更轻松地了解何时取消订阅。 – Vlad 2010-11-13 18:02:26

+0

查看我上面的代码我订阅了3个事件OnXXX方法。 PeriodViewModel不能同时删除DocumentListViewModel。 – Elisabeth 2010-11-13 20:10:22

4

您可以在MSDN上查看this article。报价:

要防止事件处理程序从 当事件是 提出被调用,只需从 事件退订。为了防止资源泄漏,在处理用户对象之前,从事件中取消订阅 是很重要的。直到你 从事件退订,构成了这一 事件发布对象的 多播委托具有 参考代表的是 封装了用户的事件 处理。只要发布 对象拥有该引用,您的 订户对象将不会收集垃圾 。

1

1)如果我总是desubscribe事件订阅呢?
通常是的。唯一的例外是当你订阅的对象不再被引用,并且很快就会被垃圾回收。

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

+1

我会指出,如果您不退订该事件,那么您的处理程序可以在完成使用该对象后调用;如果物体是一次性的,那么在物体被处理后。如果它使用由dispose释放的非托管资源,这可能会对处理程序代码产生影响。 – 2010-11-13 14:02:28

39

好吧,我们先来谈谈最后一个问题。您不能可靠地取消订阅您直接使用lambda表达式订阅的事件。你要么需要保持一个变量与代表在(所以你仍然可以使用lambda表达式)你需要使用方法组转换。

现在至于你是否真的需要取消订阅,它取决于事件生产者和事件消费者之间的关系。如果活动生产者应该活动的时间超过活动消费者的时间,那么您应该取消订阅 - 因为否则生产者会提及消费者,使其活动的时间超过应有的时间。只要生产者生产它,事件处理程序也将继续被调用。

现在在许多情况下这不是问题 - 例如,在一个表单中,引发Click事件的按钮可能会生存的时间大约与创建它的表单相同,处理器通常是订阅的。所以没有必要退订。这对于GUI非常典型。

同样,如果仅为单个异步请求创建WebClient,则订阅相关事件并启动异步请求,然后在请求完成时WebClient本身将有资格进行垃圾回收(假设您不愿意在其他地方不保留参考)。

基本上,你应该总是考虑生产者和消费者之间的关系。如果制片人的寿命超过了你想要的消费者,它会在你不再感兴趣后继续提高事件,那么你应该退订。

+0

event producer = documentListViewModel?事件消费者=上面的OnXXX方法?你的意思是如果我要删除一个documentListViewModel还有一个OnXXX方法的引用?我应该在哪里取消订阅ViewModel中的事件? – Elisabeth 2010-11-13 16:42:39

2

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

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

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

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