2016-02-13 67 views
1

在我的应用程序中,我注意到我处理事件的方式导致性能问题。性能问题 - 取消订阅事件

我想知道如果这是预期的,也许我在那里做错了什么。 有没有办法解决我的问题?

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var x = new Main(); 
      x.Init(); 

      Console.ReadLine(); 
     } 
    } 

    public class Main 
    { 
     private Bar _bar; 
     private List<Foo> _foos; 

     public Main() 
     { 
      _bar = new Bar(); 
     } 

     public void Init() 
     { 
      var sw = new Stopwatch(); 

      sw.Restart(); 
      _foos = new List<Foo>(); 
      for (int i = 0; i < 10000; i++) 
      { 
       var newFoo = new Foo(); 
       newFoo.Bar = _bar; 
       _foos.Add(newFoo); 
      } 
      sw.Stop(); 

      Console.WriteLine("Init 10.000 Foos WITH un-subscribe event: {0} ms", sw.ElapsedMilliseconds); 
      _foos.Clear(); 

      sw.Restart(); 
      _foos = new List<Foo>(); 
      for (int i = 0; i < 10000; i++) 
      { 
       var newFoo = new Foo(); 
       newFoo.BarWithout = _bar; 
       _foos.Add(newFoo); 
      } 
      sw.Stop(); 

      Console.WriteLine("Init 10.000 Foos WITHOUT un-subscribe event: {0} ms", sw.ElapsedMilliseconds); 
      _foos.Clear(); 
     } 
    } 

    public class Bar 
    { 
     public event EventHandler<string> Stuff; 

     protected virtual void OnStuff(string e) 
     { 
      var stuff = this.Stuff; 
      if (stuff != null) 
       stuff(this, e); 
     } 
    } 

    public class Foo 
    { 
     private Bar _bar; 

     public Bar Bar 
     { 
      get { return _bar; } 
      set 
      { 
       if (_bar != null) 
       { 
        _bar.Stuff -= _bar_Stuff; 
       } 

       _bar = value; 

       if (_bar != null) 
       { 
        _bar.Stuff -= _bar_Stuff; 
        _bar.Stuff += _bar_Stuff; 
       } 
      } 
     } 

     public Bar BarWithout 
     { 
      get { return _bar; } 
      set 
      { 
       if (_bar != null) 
       { 
        //_bar.Stuff -= _bar_Stuff;  
       } 

       _bar = value; 

       if (_bar != null) 
       { 
        //_bar.Stuff -= _bar_Stuff; 
        _bar.Stuff += _bar_Stuff; 
       } 
      } 
     } 

     private void _bar_Stuff(object sender, string e) 
     { 

     } 
    } 
} 

在此示例代码,我Foo类有2个属性BarBarWithoutBarWithout属性已取消订阅评论。

Main类的Init方法我建立2倍10.000Foo对象和所述第一例程设置Bar性的第二设置BarWithout属性。在我的机器上,第一个程序需要约2200毫秒,第二个程序需要约5ms。

由于差距有点巨大,我想知道是否有更有效的方法来删除事件处理程序?

顺便说一句,我知道我可以改变代码,以便Main订阅Bar的事件,并且为列表中的所有Foo对象调用一个方法,但是希望有一些“更容易”,而不需要重构现在的情况。

编辑:

具有4倍的数据(如此40.000代替10.000)的第一例程已花费〜28.000毫秒相比〜20毫秒,所以第一个例程是只用慢10倍以上4倍以上的数据。第二个例程保持不变,性能提高4倍,数据速度减慢4倍。

+2

'_bar.Stuff - = _bar_Stuff;'的问题,它是在一个MulticastDelegate为O(n)的操作。在这个测试中,它必须通过大量代表来寻找可能的匹配。在同一个_bar变量的循环中做到这一点使得它O(n^2),二次算法开始非常快地吸吮。很难给出具体的建议,代码是非常人为的,通常_bar将是一个不同的对象。 –

回答

2

让我们来看看你实际上是在做你的循环是什么:

var newFoo = new Foo(); 
newFoo.Bar = _bar; 

所以你创建一个新的Foo每次和分配(现有的)bar给它,这将导致Foo附加的事件处理器。

无论如何,从来没有一个Foo已经有Bar分配。因此,从来没有发生过“旧”Bar对象上的事件处理程序的注销,因为没有旧的Bar对象。因此,在您的二传手年初以下条件是不正确的,代码不运行:

if (_bar != null) 
{ 
    _bar.Stuff -= _bar_Stuff; 
} 

在每次迭代_barnull,所以注释掉该行并没有任何区别。

这使得下面的部分BarBarWithout之间的唯一区别:

if (_bar != null) 
{ 
    _bar.Stuff -= _bar_Stuff; 
    _bar.Stuff += _bar_Stuff; 
} 

这始终运行,因为我们始终指定一个非空Bar它。附加事件也总是运行,以至于无法发挥作用。其中只留下注销。那时我问你:你期望做什么?为什么您取消注册之后直接注册的同一个事件处理程序?

您是否试图用这种方法试图取消注册事件处理程序Foo s?那不行; _bar_Stuff是特定于当前实例的,因此它不能是另一个Foo的处理程序。

那么既然_bar_Stuff总是Foo实例的事件处理程序,因为总有一个新的Foo这意味着Bar将永远不会有在这一点上注册的事件处理程序。因此该行尝试删除从未注册的事件处理程序。正如你的基准所显示的那样,这似乎很昂贵,所以你应该避免它。

请注意,您的基准测试还有另一个问题,即_foos.Clear()。虽然这会清除列表并删除对foos的引用,但一个Bar实例仍然会注册这些事件处理程序。这意味着Bar保留对每个Foo对象的引用,防止它们被垃圾收集。此外,越频繁地运行循环,注册的事件处理程序越多,因此,取消订阅未订阅Bar的事件处理程序将需要更多时间(如果首次运行BarWithOut基准测试,您可以轻松看到)。

因此,tl; dr所有这一切都是您应该确保Foo s正确退订事件。

1

由于差距有点巨大,我想知道是否有一个更有效的方式来删除事件处理程序?

一般 - 没有。在这个特殊的情况下 - 是的。只要删除该代码即可。这是多余的。

想一想。属性设置者应该是唯一取消订阅上一个事件源并订阅新源的地方。因此,绝对没有必要退订新版本(因为你知道你的对象不应该订阅),你在做什么是没有作用的。但当然,-=操作不具备这些知识,只能通过处理程序的整个列表才能发现没有任何可删除的内容。这是每种线性搜索算法的最坏情况,并且在循环中使用时导致O(N^2)时间复杂度,因此性能上存在差异。

正确实现应该是这样的

public Bar Bar 
{ 
    get { return _bar; } 
    set 
    { 
     if (ReferenceEquals(_bar, value)) return; // Nothing to do 
     if (_bar != null) _bar.Stuff -= _bar_Stuff; 
     _bar = value; 
     if (_bar != null) _bar.Stuff += _bar_Stuff; 
    } 
}