【问题标题】:Weak events and GC弱事件和GC
【发布时间】:2016-04-21 10:26:24
【问题描述】:

当我无法确定地取消订阅时,我正在使用弱事件(否则我更喜欢 +=-= 而不是弱事件):

class SomeType
{
    public SomeType(...)
    {
        // object doesn't know when it will be removed
        WeakEventManager(SomeSource, EventArgs).AddHandler(someSourceInstance,
            nameof(SomeSource.SomeEvent), (s, e) => { ... });
    }
 }

这样,如果对象被垃圾回收,则不会调用事件处理程序。完美。

但是。如果对象还没有被垃圾回收(但是没有更多的强引用),那么事件处理程序仍然会被调用。

我的问题比较笼统:使用弱事件时应该怎么做?使用弱事件时,我是否应该期望在事件处理程序中调用无效?还是我应该force GC 来避免这种情况(某种确定性的“清理”)?还有什么?

【问题讨论】:

  • 你搞错了——如果订阅者只能通过事件处理程序保持活动状态,那么弱事件就会允许订阅者被垃圾回收。他们不会仅仅因为订阅者有资格获得 GC 就摆脱事件处理程序,这是一个副作用。您可以将其称为“无效呼叫”,但事实并非如此。所以简短的回答是“是的,你应该期待这样的电话”;更长的时间是“您甚至在做什么,这是一个问题,您是否缺少可以使您的意图明确的代码”?
  • @JeroenMostert,由于缺少-=,我发生了内存泄漏。提供退订并不简单(我没有说不可能),因此我尝试使用弱事件。他们完美地解决了内存泄漏问题,但出现了另一个问题......因此我的问题。我应该如何明确“这个对象需要调用这个方法”的意图? IDisposable?
  • IDisposable 是一种方式,是的。这是 C# 用于确定性清理的主要机制。虽然在技术上仅用于释放非托管资源,但它在框架中广泛用于确定性清理,即使不涉及非托管资源也是如此。它具有语言支持 (using) 的优势,并清楚地向开发人员发出该对象应明确发布的信号。然而,比IDisposable 更好的是找出对象的所有权规则,并让所有者负责“关闭”对象,如果可能的话。
  • 是的,这就是“所有权”的运作方式——如果直接上一级无法制定法律,则必须向上踢,以此类推。您不能让垃圾收集为您完成繁重的工作 - 它旨在节省内存(必要时!),而不是实现您可以搭载的生命周期系统。特别是,如果编写得当,即使没有垃圾收集,您的应用程序也应该正确运行,因为我们拥有(比如说)TB 级的内存并且永远不需要释放任何东西。如果不是,则说明您遇到了设计错误。
  • 为什么事件处理程序运行很重要?如果你能让它“无所谓”,你的问题就解决了。如果这不可能,那么您在某处没有在代码中表达的隐含功能需求。

标签: c# garbage-collection weak-events


【解决方案1】:

您应该始终期望在您取消注册后可能会调用事件处理程序,即使是“强”事件也是如此。这样的调用没有什么无效的。

当您查看事件处理程序的执行方式时,最简单的场景是显而易见的:

protected void OnMyEvent(object sender, EventArgs e)
{
  var ev = MyEvent;
  if (ev != null) ev(this, EventArgs.Empty);
}

如果代理在ev = MyEventev.Invoke 之间未注册,它仍会尽早收到通知。我有没有提到并发编程很困难?

但在您的情况下,问题实际上是“为什么对象不知道何时取消注册?”回答这个问题,你就会有你的解决方案。为什么调用针对不再在任何地方强引用的对象的事件处理程序是非法操作?这不像是对象被部分收集或任何东西 - 它只是还没有被收集。

【讨论】:

  • 没有可用的模式。 IDisposable?只退订?在C++ 中有你依赖的析构函数。我可以创建一个方法CallMeWhenYouDontNeedInstance(),但我不确定(我也不能确保此类对象的用户会调用它)。这是使用弱事件的主要目的:避免此类并发症。
  • @Sinatr 你不能依赖 C++ 中的析构函数,就像你不能依赖调用你的清理方法的人一样,无论是 Dispose(这是错误的,学究式的说法)与否。几乎在所有情况下,对象都应该有明确的所有权,并且当所有者完成后,它应该处置他们拥有的对象。我理解你想要做什么以及为什么,但一般来说,这种模式很难遵循并且无论你添加多少黑客都容易出错(“看,我正在注销一个事件终结者!”)。如果您可以选择使用确定性清理,请执行此操作。
  • 如果事件源的生存时间比对象长,那么您确实可以调用处理程序,即使对象即将被释放(除非您在使其符合 GC 条件之前取消订阅)。我没想到我的正常事件处理程序也有机会出现无效状态。
  • @Sinatr 在你取消订阅之前它不能有资格获得 GC,除非事件源也有资格获得 GC(这意味着取消订阅没有意义)。实际上有可能在订阅对象和通知之前收集事件源(例如,当通知发布在同步上下文上时)。确实,并发代码很难。除了终结器代码(根本不应该处理托管资源)之外,没有“即将被收集”这样的状态——如果你有参考,你就不是 GCable。即使是弱引用也是如此。
猜你喜欢
  • 1970-01-01
  • 2016-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-28
  • 2013-12-21
  • 1970-01-01
  • 2010-11-08
相关资源
最近更新 更多