2013-03-05 14 views
5

我一直在使用WeakEventManager來避免內存泄漏,並且我開始過度使用它們。 我創建擴展方法,例如,用於INotifyPropertyChanged的,如:WeakEventManager擁有對用戶的引用


public static void AddWeakPropertyChanged(this INotifyPropertyChanged item, Action handler) 
{ 
    PropertyChangedEventManager.AddHandler(item, (s, e) => handler(e.PropertyName), string.Empty); 
} 

現在我很快意識到,這是行不通的。事實上,你不能真正使用匿名方法進行弱事件處理。 (如果我理解正確,那麼編譯器爲它創建一個'閉包類'(用於存放引用的值),它具有處理程序,但由於您的閉包類沒有被引用到任何地方,GC將清除它,並且事件處理程序將不會被稱爲)

問題1:是否正確?我的意思是說它是正確的,那麼當對弱事件處理程序使用匿名方法(或lambda)時,只有在GC未同時運行的情況下才會調用處理程序(例如,它是不確定的)?

那麼,我想這樣,所以我做了一些單元測試,以確保我得到它的權利。這似乎還好吧,直到我打了以下的單元測試:


     class DidRun 
     { 
      public bool Value { get; set; } 
     } 
     class TestEventPublisher 
     { 
      public event EventHandler<EventArgs> MyEvent; 
      public void RaiseMyEvent() 
      { 
       if (MyEvent != null) 
        MyEvent(this, EventArgs.Empty); 

      } 
     } 
     class TestClosure 
     { 
      public DidRun didRun { get; set; } 
      public EventHandler<EventArgs> Handler { get; private set; } 
      public TestClosure() 
      { 
       this.Handler = new EventHandler<EventArgs>((s, e) => didRun.Value = true); 
      } 
     } 
     [TestMethod] 
     public void TestWeakReference() 
     { 
      var raiser = new TestEventPublisher(); 
      var didrun = new DidRun(); 
      var closure = new TestClosure { didRun = didrun }; 
      WeakEventManager<TestEventPublisher, EventArgs>.AddHandler(raiser, "MyEvent", closure.Handler); 
      closure = null; 

      GC.Collect(); 
      GC.Collect(); 
      raiser.RaiseMyEvent(); 
      Assert.AreEqual(false, didrun.Value); 
     } 

問題2:任何人都可以解釋我爲什麼會發生這種測試失敗?期望:在這裏,我沒有任何關閉(我把它們拿出來,以確保發生了什麼),我只是有一個對象(閉包),訂閱了一個與WeakEventManager事件,然後我放棄引用它(closure = null;)。

我期待着2 GC.Collect()調用,以清理我的舊閉包類,所以WeakEventManager將放棄訂閱者,並且不運行處理程序,但測試失敗。有任何想法嗎?

編輯:對不起,一般的論據是不可見的,現在他們

回答

3

你是正確的,因爲GC將收集這是在你的拉姆達創建的閉包如果它沒有提及。

在您的單元測試中,您將TestClosure的本地實例置空,但您已將處理程序的硬引用傳遞到WeakEventManager,而不是TestClosure的實例。因此,處理生活在...

我相信這些例子表明與關閉你的麻煩:

class DidRun 
{ 
    public bool Value { get; set; } 
} 

class TestEventPublisher 
{ 
    public event EventHandler<EventArgs> MyEvent; 
    public void RaiseMyEvent() 
    { 
     if (MyEvent != null) 
      MyEvent(this, EventArgs.Empty); 
    } 
} 

class TestClosure 
{ 
    static public EventHandler<EventArgs> Register(TestEventPublisher raiser, DidRun didrun) 
    { 
     EventHandler<EventArgs> handler = (s, e) => didrun.Value = true; 
     WeakEventManager<TestEventPublisher, EventArgs>.AddHandler(raiser, "MyEvent", handler); 
     return handler; 
    } 
} 

[TestMethod] 
public void Test1() 
{ 
    var raiser = new TestEventPublisher(); 
    var didrun = new DidRun(); 

    TestClosure.Register(raiser, didrun); 

    // The reference to the closure 'handler' is not being held, 
    // it may or may not be GC'd (indeterminate result) 

    raiser.RaiseMyEvent(); 
    Assert.IsTrue(didrun.Value); 
} 

[TestMethod] 
public void Test2() 
{ 
    var raiser = new TestEventPublisher(); 
    var didrun = new DidRun(); 

    // The reference to the closure 'handler' is not being held, it's GC'd 
    TestClosure.Register(raiser, didrun); 

    GC.Collect(); 
    GC.Collect(); 

    raiser.RaiseMyEvent(); 
    Assert.IsFalse(didrun.Value); 
} 

[TestMethod] 
public void Test3() 
{ 
    var raiser = new TestEventPublisher(); 
    var didrun = new DidRun(); 

    // Keep local copy of handler to prevent it from being GC'd 
    var handler = TestClosure.Register(raiser, didrun); 

    GC.Collect(); 
    GC.Collect(); 

    raiser.RaiseMyEvent(); 
    Assert.IsTrue(didrun.Value); 
} 

至於你原來的問題,你可以嘗試保存處理器(關閉),以防止它被GC'd。一個ConditionalWeakTable應該對這項工作:

// ConditionalWeakTable will hold the 'value' as long as the 'key' is not marked for GC 
static private ConditionalWeakTable<INotifyPropertyChanged, EventHandler<PropertyChangedEventArgs>> _eventMapping = 
    new ConditionalWeakTable<INotifyPropertyChanged, EventHandler<PropertyChangedEventArgs>>(); 

public static void AddWeakPropertyChanged(this INotifyPropertyChanged item, Action<string> handlerAction) 
{ 
    EventHandler<PropertyChangedEventArgs> handler; 

    // Remove any existing handler for this item in case it's registered more than once 
    if (_eventMapping.TryGetValue(item, out handler)) 
    { 
     _eventMapping.Remove(item); 
     PropertyChangedEventManager.RemoveHandler(item, handler, string.Empty); 
    } 

    handler = (s, e) => handlerAction(e.PropertyName); 

    // Save handler (closure) to prevent GC 
    _eventMapping.Add(item, handler); 

    PropertyChangedEventManager.AddHandler(item, handler, string.Empty); 
} 

class DidRun 
{ 
    static public string Value { get; private set; } 
    public void SetValue(string value) { Value = value; } 
} 

[TestMethod] 
public void Test4() 
{ 
    var property = new ObservableObject<string>(); 

    var didrun = new DidRun(); 
    property.AddWeakPropertyChanged(
     (x) => 
     { 
      didrun.SetValue("Property Name = " + x); 
     }); 

    GC.Collect(); 
    GC.Collect(); 

    property.Value = "Hello World"; 

    Assert.IsTrue(DidRun.Value != null); 
} 
+0

哇,非常感謝,我被這個困惑,而事實上我忽略,即在值傳遞是很難參照處理。現在我明白了。再次感謝您的幫助! – MBoros 2013-10-22 10:54:52

相關問題