2015-04-03 22 views
1

(前綴:對於那些誰看到這一點,認爲TL; DR,實際的問題是,在年底)在關閉以避免內存保持引用正確處理泄漏

由於在C#中發現lambda表達式和委託,我已經成爲他們的大消費者。然而,當涉及到釋放關閉中維護的對象的內存時,尤其是在處理嵌套閉包時,我一直存在擔憂。例如,考慮從我正在寫的我認爲是「合適的」行爲的一組課程的下面。

public static PropertyChangedEventHandler GetHandler<TDependant, TDependantHost, TFoundation, TFoundationHost> 
    (
     this TDependantHost target, 
     PropertyChangedEventHandler invokeTarget, 
     Expression<Func<TDependantHost, TDependant>> dependantRef, 
     Expression<Func<TFoundationHost, TFoundation>> foundationRef, 
     Expression<Func<TDependantHost, TFoundationHost>> foundationHostRef 
    ) 
    where TDependantHost : ISupportsDependencyManager 
    where TFoundationHost : class, INotifyPropertyChanged 
{ 
    string foundationName = GetPropertyInfo(foundationRef).Name; 
    string dependantName = GetPropertyInfo(dependantRef).Name; 
    string foundationHostName = GetPropertyInfo(foundationHostRef).Name; 
    Func<TDependantHost, TFoundationHost> foundationHostRefCompiled = foundationHostRef.Compile(); 
    PropertyChangedEventHandler oOut = null; 

    // Complex situation. This is more complex because whilst TDependantHost bears a relationship to TFoundationHost 
    // the actual dependency is on a property in TFoundationHost. 
    // oOut is the property changed handler that will be attached to target, so it needs to 
    // - Raise changed events whenever foundationHostRef would evaluate to a different object 
    // - Whenever that change occurs, attach a new PropertyChangedEventHandler to the new foundationHost 
    // - ... which also handles removal of itself from target so as to guarantee 
    oOut = (s, e) => 
    { 
     var sender = s as INotifyPropertyChanged; 
     if (sender == null) 
      return; 
     if (e.PropertyName == foundationHostName) 
     { 
      // The Foundation Host has changed. So we need to attach a new inner PropertyChangedEventHandler to it. 
      PropertyChangedEventHandler innerHandler = null; 
      innerHandler = 
       (s2, e2) => 
       { 
        // Caller safety... 
        var innerSender = s2 as TFoundationHost; 
        if (innerSender == null) 
         return; 

        // Check and see if this eventhandler still points to the right object 
        // If it does, we'll keep going - otherwise, got to remove the event handler and return 
        if (foundationHostRefCompiled(target) != innerSender) 
        { 
         innerSender.PropertyChanged -= innerHandler; 
         return; 
        } 

        // Now we know that the inner handler is executing for an entity that still bears the correct 
        // relationship to target. So we just check the same way as usual - did foundation just change? 
        // If so, so did dependant 
        if (e2.PropertyName == foundationName) 
         invokeTarget.SafeInvoke(target, dependantName); 
       }; 

      // since the foundation has shifted, the dependency will also have changed 
      // Raise a handler for it. 
      invokeTarget.SafeInvoke(sender, dependantName); 
     } 
    }; 
    return oOut; 
} 

什麼,這是應該做的(它可能 - 仍需雖然測試,我想我需要一對夫婦在這裏和那裏null檢查)是:

  • 當引用類型目標的變化特性,提出一個PropertyChanged事件的相關屬性(由dependantRef標識)
  • 此外,附加PropertyChanged處理程序foundationHost,因此,如果基礎發生變化,目標得到通知
  • 通過在執行事件處理程序之前解析foundationHostRef,確保目標不會收到與此不相關的foundationHost的通知,並在發生此情況時將其解除。

因此,使用上述的邏輯,和撇開,它們嵌套foundationHost S上的問題多於一層深(這是在正在進行的工作),它看起來像,迄今爲止,通過foundationHostRef提及的任何對象將保持對目標的封閉引用,即使它不再與其相關聯,至少在它試圖提出事件之前。

現在我對此的理解是,我創建的事件處理程序可以輕鬆地停止目標占用的內存被釋放。所有需要發生的事情都是某個對象在某個時間點佔用foundationHostRef,然後在其他地方重新分配,並且使用壽命比target更長,具體取決於target正在做什麼,可能來自煩人的任何地方(target是一個單身人士, (佔用大量內存)到災難性的(target在程序生命週期中被創建並且被禁止了數千次,並且具有佔用大量內存的一些屬性,並且GC從不收集它)。

所以,我的問題:什麼建立在保護是否存在,如果有的話,反對這種事情?如果沒有,我應該如何調整我的代表/ lambda,以便它們不再是邪惡的?

回答

1

您可以將兩個兩件事情來緩解這種風險:

  1. 如果你認爲一個特定的代碼塊是在風險儘量不要有使用lambda表達式。關閉舊的方法:通過在類上創建實例方法。
  2. 安裝Resharper Allocation Plugin。看到所有常見C#習慣用法所做的分配是相當有教育意義的。該插件告訴你哪些變量已關閉。 R#也會警告「隱式」捕獲的變量(不確定這意味着什麼,但它聽起來像是對你有用的警告)。

它看起來像一個曾經被foundationHostRef提到將保持關閉參照目標,即使它不再與它

我不跟隨代碼的任何對象完全如上所述。永遠不會有無數的對象被封閉引用。對象引用的數量是不變的。

但是,可能存在的一個問題是,似乎未被使用或超出範圍的局部變量仍然可能會產生強烈的參考。這是因爲C#編譯器在關閉變量未使用時不會清空它們。它對於普通的當地人來說並不是這樣,但是JIT足夠聰明,可以做同樣的事情。

在每種情況下,我都知道(儘管它可能不受C#規範的保證),但要清除的變量明確無效。

+0

ReSharper可能會幫助,但我的試用許可證已過期,並且暫時沒有發現我使用它足以證明購買完整許可證的理由。儘管知道未使用的局部變量仍然可以產生強烈的參考,但有用。我正在考慮將參數轉換爲'WeakReference'以便在lambdas中使用,但是這聽起來不夠遠。不過,如果我在生成'WeakReference'之後將參數歸爲空,那可能會起作用,我在想...... – tobriand 2015-04-03 11:56:25

+0

請注意,WeakReference引入了語義變化。如果不存在其他參考,WeakReference最終將在隨機時間點變爲空。這通常是一種更安全,更安全的方法來管理引用,而不是使用弱引用。 – usr 2015-04-03 11:58:43

+0

非常真實 - 我剛剛有一種感覺,重構一個弱引用並檢查一致性要比依靠強大的引用更容易,我不想在Target發佈後實施其他引用。這聽起來像是唯一的方法是在(擴展名)方法結束之前將target和invokeTarget設爲null。因此,我可能因此大部分時間都會承受語義變化,因爲我首先檢查空位和實時狀態。 – tobriand 2015-04-03 12:42:18