2016-02-13 69 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; 
    } 
}