3

更新:我编写了一个程序来测试我在下面提到的每种技术的内存影响。毫不奇怪,我发现,果然,使用 .NET 事件的传统方法比其他方法产生更多的垃圾(意思是,它确实会产生垃圾,而其他两种策略似乎都不会产生任何垃圾)。

真的,我应该一直强调,我对.NET 事件中参数的内存开销比速度方面的成本更感兴趣。最终我不得不承认,就所有实际目的而言——无论是内存还是速度——成本都可以忽略不计。尽管如此,我还是觉得有趣的是,以“传统”方式引发大量事件确实会花费一些东西——而且在极端情况下,它甚至会导致第一代垃圾收集,这可能会或可能无关紧要,具体取决于情况(根据我的经验,系统需要越“实时”,就越需要注意垃圾的产生位置以及如何在适当的情况下将其最小化)。TEventArgs


这似乎是一个愚蠢的问题。例如,我意识到 Windows 窗体可以很容易地被认为是“高性能”场景,数百甚至数千个事件一直在非常快速地连续引发(例如,Control.MouseMove事件)。但我仍然想知道,当预期该类将用于高性能、时间关键的代码时,使用 .NET 事件设计一个类是否真的合理。

我主要关心的是一个约定,即EventHandler<TEventArgs>对所有事件都使用类似的东西,其中TEventArgs派生自EventArgs并且很可能是每次引发/处理事件时都必须实例化的类。(如果它只是 plain EventsArgs,显然,这不是EventArgs.Empty可以使用的情况;但假设类型中包含任何有意义和非常量的信息TEventArgs,则可能需要实例化。)看起来这会导致 GC 压力大于我希望创建一个高性能库。

也就是说,我能想到的唯一选择是:

  1. 对事件使用非常规的委托类型(即 not EventHandler<TEventArgs>),仅采用不需要对象实例化的参数,例如int,double等(甚至string, 并传递对现有字符串对象的引用)。
  2. 完全跳过事件并使用虚拟方法,强制客户端代码根据需要覆盖它们。这似乎与之前的想法具有基本相同的效果,但方式更加可控。

我对 .NET 事件的 GC 压力的担忧一开始就没有根据吗?如果是这样,我在那里错过了什么?或者,有没有比我刚刚列出的两个更好的第三种选择?

4

3 回答 3

4

不,Winforms 事件发生在人类时间,而不是 CPU 时间。没有人能以足够快的速度移动鼠标来对现代机器施加任何严重的压力。每个单独的遍历像素都没有消息。

更重要的是,委托参数对象始终是 gen #0 对象。他们没有坚持足够长的时间来获得晋升。分配和垃圾收集它们都非常便宜。这不是一个真正的问题,不要追那个鬼。

于 2010-12-27T21:05:20.393 回答
3

在你做任何事情之前,你应该考虑做一些分析,以确保这实际上会出现一个真正的问题。对于像 UI 鼠标移动这样的情况,与机器相比,事件发生的频率如此之低,以至于它通常对 GC 行为的影响可以忽略不计。请记住,.NET GC 非常擅长收集对它们的引用很少的短寿命对象——它是在许多地方引用的中长寿命对象,这可能会产生问题。

然而,如果(出于某种原因)这确实被一个问题证明了,那么有几种可能的方法可以降低成本。

  1. 使用非标准事件委托签名。您已经将此确定为一种可能的替代方案 - 但是,您始终可以使用将事件参数的类型限制为结构的通用签名。将结构作为参数传递应该可以减少在堆上分配参数的情况,但代价是创建此数据的副本。

  2. 使用 TEventArgs 享元实现,该实现在使用后回收事件参数的实例。这可能是一个棘手的提议,因为您需要确保事件处理程序永远不会存储参数的实例以供在其他地方使用。然而,如果实施得当,这种模式可以显着减少需要管理和收集的轻量类型实例的数量。

于 2010-12-27T21:10:26.613 回答
2

我怀疑你的担心是有根据的,但确实有必要看一个具体的案例。

例如,在任何特定示例中,stackshots 都会告诉您哪些成本足够值得关注。可能的情况是,事件对象的创建/销毁成本不高,而是它们触发的处理。

这些天来,我经常看到的性能问题是“失控通知”。该事件可能只是设置一个对象的属性。没什么大不了的,对吧?但是随后该属性设置可能会被超类拦截,该超类可能会查找对象所在的集合并发送通知以在这些集合中添加/删除/重新定位对象,这可能导致窗口无效或菜单项已创建/已删除,或要展开/关闭的树控件项目,等等...

于 2010-12-27T20:46:51.430 回答