您如何处理高性能场景中的“事件”?

时间:2010-12-27 20:40:50

标签: .net performance events event-handling eventargs

更新:我wrote a program来测试我在下面提到的每种技术的内存含义。不是令人惊讶的是,我发现,使用.NET事件的传统方法确实产生了比其他方法更多的垃圾(意思是,它实际上创造了垃圾,而不是其他两种策略)两者似乎都不会产生任何垃圾。

真的,我应该一直强调,我对.NET事件中TEventArgs参数的内存开销更感兴趣,而不是速度方面的成本。最后,我必须承认,出于所有实际目的 - 在内存速度方面 - 成本可以忽略不计。尽管如此,我认为有趣的是,以“常规”的方式提高了很多事件 - 在极端情况下甚至会导致第1代垃圾收集,根据具体情况可能或不重要(系统需要的“实时”越多,根据我的经验,注意垃圾的创建地点和方法越重要在适当的地方尽量减少。)


这似乎是一个愚蠢的问题。例如,我意识到Windows Forms可以很容易地被视为一种“高性能”场景,其中有数百甚至数千个事件一直在非常快速地连续发生(例如,Control.MouseMove事件)。但我仍然想知道,当预计该类将用于高性能,时间要求严格的代码时,设计一个带有.NET事件的类是否合理。

我所关注的主要问题是,对于所有事件都使用类似EventHandler<TEventArgs>的约定,其中TEventArgs派生自EventArgs并且很可能是必须被实例化的类每次提出/处理事件。 (如果它只是简单的EventsArgs,显然不是这种情况,因为可以使用EventArgs.Empty;但假设任何有意义且非常量的信息包含在{{ 1}}类型,可能需要实例化。)这似乎导致GC压力高于我对高性能库创建的预期。

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

  1. 对事件使用非常规委托类型(即,不是TEventArgs),仅采用不需要对象实例化的参数,例如EventHandler<TEventArgs>int等(偶数{{ 1}},并传递对现有字符串对象的引用。)
  2. 完全删除事件并使用虚拟方法,强制客户端代码根据需要覆盖它们。这似乎与之前的想法基本上具有相同的效果,但是以一种更加可控的方式。
  3. 我对.NET事件的GC压力的担忧是否一开始没有根据?如果是这样,我在那里错过了什么?或者,是否有一些第三种选择比我刚才列出的更好?

3 个答案:

答案 0 :(得分:4)

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

更重要的是,委托参数对象始终是gen#0对象。他们不会坚持到足以让自己得到晋升。分配和垃圾收集它们都很便宜。这不是一个真正的问题,不要追逐鬼。

答案 1 :(得分:3)

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

但是,如果(出于某种原因)这确实存在问题,可以通过几种方法来降低成本。

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

  2. 使用TEventArgs flyweight实现,在使用后回收事件参数的实例。这可能是一个棘手的主张,因为您需要确保事件处理程序永远不会存储用于其他地方的参数实例。但是,如果正确实现,此模式可以显着减少需要管理和收集的轻量级类型的实例数。

答案 2 :(得分:2)

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

例如,在任何特定的例子中,stackshots会告诉你什么是成本足以被关注。可能的情况是,成本不是在事件对象的创建/销毁中,而是在它们触发的处理中。

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