2011-10-04 43 views
13

我一直在修復winforms應用程序中的一些內存泄漏問題,並注意到一些不顯式放棄的對象(開發人員沒有稱爲Dispose方法)。 Finalize方法的實現也沒有幫助,因爲它不在if (disposing)子句中。所有的靜態事件註銷和收集清除已放入if (disposing)條款。最好的做法是調用的Dispose如果對象是一次性的,但不幸的是如果有非託管對象,靜態的事件處理程序和需要處置時,清除一些管理的集合這種情況有時一次性實現 - 應該如何處理'if(disposing)'

。有什麼方法可以決定什麼應該進入以及應該從if (disposing)條款中排除。

Dispose method.

// Dispose(bool disposing) executes in two distinct scenarios. 
// If disposing equals true, the method has been called directly 
// or indirectly by a user's code. Managed and unmanaged resources 
// can be disposed. 
// If disposing equals false, the method has been called by the 
// runtime from inside the finalizer and you should not reference 
// other objects. Only unmanaged resources can be disposed. 
protected virtual void Dispose(bool disposing) 
{ 
    if (!disposed) 
    { 
     if (disposing) 
     { 
      // Free other state (managed objects). 
     } 

     // Free your own state (unmanaged objects). 
     // Set large fields to null. 
     disposed = true; 
    } 
} 

It says管理對象應在if (disposing)通常只有執行時顯式調用Dispose方法由開發商。如果Finalize方法已經實現並且開發人員忘記調用Dispose方法,那麼通過Finalizer進行的執行不在if (disposing)部分。

以下是我的問題。

  1. 如果我有導致內存泄漏的靜態事件處理程序,我應該在哪裏取消註冊它們?有沒有if (disposing)條款?

  2. 如果我有一些導致內存泄漏的集合,我應該在哪裏清除它們?有沒有if (disposing)條款?

  3. 如果我使用第三方的一次性對象(例如:devExpress winform控件),我不確定它們是託管對象還是非託管對象。假設我想在處理表單時處理它們。我怎麼知道什麼是管理的,什麼是非管理對象?一次性使用不是這樣說的嗎?在這種情況下,如何確定應該進入哪些內容以及從if (disposing)條款中應該列出哪些內容?

  4. 如果我不確定某件事情是否管理或不受管理,那麼if (disposing)子句中處理/清除/取消註冊事件的不良後果是什麼?假設它在處置之前檢查爲空?

編輯

我的意思是事件未登記是一樣的東西下面。 Publisher是一個長期存在的實例,下面一行是訂閱者的構造函數。在這種情況下,用戶需要取消註冊該事件並在發佈者之前進行處置。

publisher.DoSomeEvent += subscriber.DoSomething; 

回答

0

我應該去 '如果(處置)'

所有管理對象應該裏面去,如果(處置)條款。被管理的對象不應該放在它的外面(這將通過最終確定來執行)。

原因是垃圾收集器定稿過程可以執行Dispose(false)如果該類具有析構函數。通常情況下,只有存在非託管資源時纔有一個析構函數。垃圾收集器的最終化並沒有執行Finalize方法的特定順序。所以,其他管理對象可能不會在發生時間內存在於內存中。

0

如果我有靜態的事件處理程序,導致內存泄漏我應該在哪裏註銷呢? (處置)條款中是否出現?

Dispose方法被稱爲上,其中爲靜態事件處理程序是在類層次使用實例。所以你不應該取消註冊它們。通常是靜態的事件處理程序應取消其註冊時的類卸載或應用程序的執行過程中某些時候,你得到這個事件處理程序沒有更多的要求。

對於所有管理和未管理的資源更好地實現IDisposable模式。 看到這裏 http://msdn.microsoft.com/en-us/library/fs2xkftw%28VS.80%29.aspxFinalize/Dispose pattern in C#

+0

實例訂閱靜態事件並不罕見。這些實例應該在if(Disposing)子句中取消訂閱。 – supercat

2

廣義上說,管理資源配置中if (disposing)和非託管資源在它之外。該Dispose模式的工作原理是這樣的:

  1. if (disposed) {

    如果此對象已經設置,不處置它第二次。

  2. if (disposing) {

    如果編程(true)被要求處置,處置該對象擁有管理資源(IDisposable的對象)。

    如果處置是由垃圾收集器(false),因爲垃圾收集器可能已經丟棄了所有的管理資源,請不要將其管理的資源,並會definitelty處理他們的應用程序終止前引起的。

  3. }

    處置非託管資源,並釋放所有對它們的引用。步驟1確保只發生一次。

  4. disposed = true

    標示爲設置以防止重複處理該對象。重複處理可以在步驟2或3

問題1
不要在Dispose方法處置它們都導致一個NullReferenceException。如果您處理了該課程的多個實例,會發生什麼?儘管已經處置了靜態成員,但每次都會處理靜態成員。我找到的解決方案是處理AppDomain.DomainUnloaded事件,並在那裏執行靜態處置。

問題2
這一切都取決於集合的項目是託管還是非託管。可能值得創建託管包裝,爲您正在使用的任何非託管類實現IDisposable,以確保管理所有對象。

問題3
IDisposable是一個託管界面。如果一個類實現了IDisposable,它是一個託管類。在if (disposing)內部處理管理對象。如果它沒有實現IDisposable,則它可以被管理並且不需要處理,或者不被管理,並且應該被放置在if (disposing)之外。

問題4
如果應用程序意外終止或不使用人工處理,垃圾收集處置按隨機順序的所有對象。子對象可以在其父母被處置之前被處置,導致父母第二次處置該子女。大多數託管對象可以安全地多次處理,但前提是它們已經正確構建。如果一個對象被多次處置,你會冒(儘管不太可能)導致gargabe集合失敗。

+0

垃圾回收器永遠不會調用disposer。永遠。 C#有一個析構函數(a.k.a和一個.Net終結器),當且僅當該對象被垃圾收集時纔會被調用。 – Spence

+0

對象的析構函數的實現是可選的。 – Spence

+0

@Spence,你是對的。 'Finalize'方法(我使用VB)是可選的,但如果您有非託管資源需要處理,則必須使用該方法。它被用作垃圾收集器入口點到'Dispose(Boolean)'方法。 –

0

聽起來你主要有管理對象,即實現IDisposable的對象。非託管代碼可能是IntPtr或句柄,這通常意味着調用非託管代碼或P/Invoke來獲取它們。

  1. 正如Maheep所指出的那樣,Dispose並不是爲此而設計的。當一個對象完成接收事件時,它應該註銷自己。如果這不可能,請考慮使用WeakReferences。

  2. 這可能不應該在處置,除非這些集合包含需要處理的對象。如果它們是一次性對象,那麼它應該放在「如果處置」塊中,並且應該在集合中的每個項目上調用處置。

  3. 如果實現IDisposable它的管理

  4. 當這正是作爲「如果(處置)」塊手段之外終結叫你不應該訪問其他託管代碼的對象。

3

這裏要記住的關鍵是IDisposable的目的。它的工作是確定性地幫助你你的代碼持有的發佈資源,之前對象被垃圾收集。這實際上是C#語言團隊選擇關鍵字using的原因,因爲括號決定了應用程序所需的對象及其資源的範圍。

例如,如果打開與數據庫的連接,則需要釋放該連接並在完成處理後儘快關閉它,而不是等待下一次垃圾回收。這是您實施處理器的地點和原因。

第二種情況是協助非託管的代碼。實際上,這與C++/C API調用操作系統有關,在這種情況下,您有責任確保代碼不會泄露。儘管許多.Net被寫入簡單的P/Invoke到現有的Win32 API,但這種情況很常見。從操作系統(例如Mutex)封裝資源的任何對象都將實現Disposer,以便您安全並確定地釋放其資源。

但是,這些API將實施析構函數,以保證,如果你不正確使用資源,它不會被操作系統泄漏。當你的終結器被調用時,你不知道你引用的其他對象是否已經被垃圾收集,這就是爲什麼對它們進行函數調用是不安全的(因爲它們可能拋出NullReferenceException),只有非託管引用根據定義不能垃圾收集)將提供給終結者。

希望能有所幫助。

+0

乾杯。更新了我的答案。 – Spence

0

除非一個類的唯一目的是封裝某些資源(*),如果放棄它,則需要清理它,它不應該有一個終結器,並且不應該使用值爲False的方式調用Dispose(bool) ,但調用Dispose(False)應該沒有效果。如果一個繼承類需要持有一個需要清理的資源(如果被放棄),它應該將該資源封裝到專門用於該目的的對象中。這樣,如果主對象被拋棄,並且沒有其他人持有對封裝資源的對象的引用,那麼該對象可以執行清理,而無需保持主對象在額外的GC循環中保持活動狀態。

順便提一句,我不喜歡微軟處理Disposed標誌。我建議非虛擬Dispose方法應該在Interlocked.Exchange中使用整數標誌,以確保從多個線程中調用Dispose將僅導致一次執行處理邏輯。該標誌本身可能是私有的,但應該有一個受保護的和/或公共的Disposed屬性,以避免要求每個派生類實現自己的處置標誌。 (*)資源不是某種特定類型的實體,而是一個鬆散的術語,它包含任何類別可能要求某個外部實體代表其執行的任何事情,該外部實體需要被告知停止這樣做。最典型的是,外部實體將授予該類獨佔使用的東西(無論是內存區域,鎖,GDI句柄,文件,套接字,USB設備等),但在某些情況下,外部實體可能被要求肯定地做某事(例如,每次發生事件時運行一個事件處理程序)或持有某些東西(例如線程靜態對象引用)。 「非託管」資源是一種如果放棄不會被清理的資源。

順便說一下,請注意,雖然微軟可能已經打算封裝非託管資源的對象應該清理它們,如果放棄,有一些類型的資源真的不切實際。例如,考慮將對象引用存儲在線程靜態字段中的對象,並在Dispose'd時自動清空該對象引用(處置將自然必須發生在創建對象的線程上)。如果對象被放棄但線程仍然存在(例如在線程池中),那麼線程靜態引用的目標可以很容易地無限期地保持活動狀態。即使沒有任何對被拋棄對象的引用使其Finalize()方法運行,被棄用對象也很難找到並銷燬位於某個線程中的線程靜態引用。