【问题标题】:Unsubscribing from events - performance hit?取消订阅活动 - 性能受到影响?
【发布时间】:2014-06-16 07:51:45
【问题描述】:

考虑以下代码(来自性能报告):

这是属性通知侦听器组件的一部分。方法OnItemPropertyChanged 是带有PropertyChangedEventHandler 签名的私有实例绑定方法。此方法被调用了大约 100.000 次,并导致应用程序出现严重延迟。

是否存在与(取消)订阅事件相关的性能注意事项?有没有解释为什么这会导致这样的性能下降?

【问题讨论】:

  • 我认为您的问题在于此方法被调用了大约 100.000 次,而不是取消订阅本身。也许你应该考虑重新设计
  • 为什么这个事件被调用了 100,000 次?
  • 您的所有项目都已更改?如果这是真的,你真的有问题;否则在你真正需要的地方调用它
  • 内存中有 100k 个项目,每个项目都有一个侦听器。
  • 如果这 100k 项中的每一项都需要调用 PropertyChanged 事件,我认为您应该重新考虑您的应用程序的设计

标签: c# event-handling


【解决方案1】:

首先要注意的是:

notificationItem.PropertyChanged -= OnItemPropertyChanged;

实际上分配了一个新的代表。这也意味着等价测试不能在 identity 等价处短路 - 它必须执行方法/目标等价(即不同的委托实例,但相同的目标/方法,因此被认为是等价的代表组合的目的)。

我会尝试首先将使用单个委托实例,即

void OnItemPropertyChanged(object sender, PropertyChangedEventArgs args) {...}

private readonly PropertyChangedEventHandler sharedHandler;
public YourType() { // constructor
    sharedHandler = OnItemPropertyChanged;
}

然后当你订阅时,而不是:

notificationItem.PropertyChanged += OnItemPropertyChanged;

notificationItem.PropertyChanged -= OnItemPropertyChanged;

改用:

notificationItem.PropertyChanged += sharedHandler;

notificationItem.PropertyChanged -= sharedHandler;

至少值得一试。

【讨论】:

  • 为什么 CLR/Compiler/... 是这样设计的?难道不能自动重构这种情况,所以它总是以某种方式在内部使用 ReferenceEquals 吗?我的意思是编译器(或抖动)可以提取目标方法并直接比较它。难道不能以某种方式在较低的层次上实现它吗? (CLR、Jit 或作为语法糖,如 var/foreach/enumerators)
  • @Felheart 它不能使用ReferenceEquals 因为它不是同一个委托实例PropertyChanged -= OnItemPropertyChanged 本质上是PropertyChanged -= new PropertyChangedEventHandler(this.OnItemPropertyChanged),实际上thisOnItemPropertyChanged 是分开提供的。管道通过PropertyChangedEventHandler 实例,而不是this / OnItemPropertyChanged
【解决方案2】:

注意94.2%相对执行时间有关。所以取消订阅会占用94.2% 方法的总执行时间Disable(..)。并且考虑到其他原始代码是castnull 检查,这是正常的。

真正的问题,imo(即使与性能有关的任何事情都与具体的执行上下文严格相关)是此方法被称为100.000 次。

【讨论】:

    【解决方案3】:

    关于许多 cmets 提出的设计缺陷:我们强烈认为用户不会动摇将具有 100k 个对象的对象图作为一个整体放入用户界面;这是持续改进过程的一部分,有望在未来得到解决。

    sharedHandlerMethod 引用传递给取消订阅运算符之间没有显着差异。

    使用weak event manager 类可以消除性能损失;有或没有创建像Marc Gravell 这样的代表建议。也许这与此类以不同的方式创建自己的事件侦听器有关,而不是使用其参数中提供的方式。我研究了source,但找不到解释为什么这种模式似乎工作得很快(因为+=-= 运算符仍然被调用)。

    【讨论】:

    • 我希望它仍然需要相同的时间,它只是发生在您的主执行线程之外。 WeakEventManager 模式看起来像是在对象被垃圾回收时(或不久之后)分离处理程序。这意味着它将在后台线程上发生,用户不必等待它发生。
    • 我们手动调用 RemoveHandler ;这应该立即删除事件处理程序?
    猜你喜欢
    • 1970-01-01
    • 2020-04-27
    • 1970-01-01
    • 2019-03-16
    • 1970-01-01
    • 1970-01-01
    • 2010-11-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多