2012-04-07 104 views
4

在這裏和那裏,人們一直在討論由於未發佈的事件監聽器而發生的內存泄漏。我認爲這是非常重要的問題。非常認真和非常重要......如果它真的存在。.NET中由於事件處理程序而導致內存泄漏的示例?

我曾嘗試自己來重現問題,但我所有的努力都失敗了:我不能讓我的應用程序泄漏內存:( 雖然這聽起來不錯,我還是很擔心:也許我失去了一些東西

因此,也許有人可以提供

我創建了一個小VB.NET應用程序作爲演示一個非常簡單的源代碼示例,這會導致內存泄漏:它包含一個Windows窗體和一類

Windows窗體:它有一個集合對象(名爲「c」)和兩個按鈕:一個用於向colle添加10個項目ction和另一個清除集合:

Public Class Form1 

Dim c As New Collection 

Private Sub btnAddItem_Click(sender As System.Object, e As System.EventArgs) Handles btnAddItem.Click 
    For i As Integer = 1 To 10 
     Dim m As New MyType 
     c.Add(m) 
    Next 

    Me.Text = c.Count 
End Sub 

Private Sub btnClear_Click(sender As System.Object, e As System.EventArgs) Handles btnClear.Click 
    For Each item As MyType In c 
     item.Dispose() 
    Next 
    c.Clear() 

    Me.Text = c.Count 
End Sub 
End Class 

的MyType類:它具有大m_Image對象,這是很大的,所以你可以看到你的內存真的採取MyType的情況下:)

Imports System.Drawing 

Public Class MyType 
Implements IDisposable 

Private m_Image As Bitmap 

Public Sub New() 
    AddHandler Application.Idle, AddressOf Application_Idle 

    m_Image = New Bitmap(1024, 1024) 
End Sub 

Private Sub Application_Idle(sender As Object, e As EventArgs) 

End Sub 

#Region "IDisposable Support" 
Private disposedValue As Boolean 

Protected Overridable Sub Dispose(disposing As Boolean) 
    If Not Me.disposedValue Then 
     If disposing Then 
      m_Image.Dispose() 
     End If 
    End If 
    Me.disposedValue = True 
End Sub 

Public Sub Dispose() Implements IDisposable.Dispose 
    Dispose(True) 
    GC.SuppressFinalize(Me) 
End Sub 
#End Region 

End Class 
+0

它在某種程度上取決於您對內存泄漏的定義以及在什麼情況下。我們在談論一個Web應用程序或桌面應用程序嗎? – 2012-04-07 16:53:21

+0

好吧,如果它有所作爲,最好查看兩種應用程序類型的代碼示例:桌面和網頁。 – Dima 2012-04-07 17:22:26

回答

11

這裏是一個非常簡單的例子

class MyType 
{ 
    public static event EventHandler ExampleEvent; 

    public MyType() 
    { 
     ExampleEvent += (sender, e) => OnExampleEvent(); 
    } 
    private void OnExampleEvent() { } 
} 

MyType任何實例將訂閱ExampleEvent事件。這個事件沒有附加到任何特定的對象,因此它永遠不會留下記憶。這將在應用程序的持續時間內保存內存中的所有實例MyType

編輯

至於要求在這裏的評論是MyType實例留在內存中的演示不久後它不再@使用

class Program 
{ 
    static void Main(string[] args) 
    { 
     WeakReference weakRef = new WeakReference(new MyType()); 
     for (var i = 0; i < 10; i++) 
     { 
      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 
     } 

     Console.WriteLine("Still Alive: {0}", weakRef.IsAlive); 
    } 
} 
+1

我不確定這是一個內存泄漏的例子,因爲你特別訂閱了一個靜態全局事件。這有點像說你有內存泄漏,因爲在應用程序退出之前,main不會退出。 – 2012-04-07 16:50:52

+1

@MystereMan OP要求提供基於事件的內存泄漏,這是一個例子。對於任何事件的壽命都大於訂閱該事件的對象的預期壽命時間,這可以重複。 – JaredPar 2012-04-07 16:51:57

+0

@MystereMan我的壞。編輯我的評論 – JaredPar 2012-04-07 16:54:32

0

更多的調查之後(感謝線索JaredPar),我發現當我們有這樣的條件時會發生內存泄漏:

  1. 創建參考REF1到具有公開程序或 函數(例如程序PRC1())的新對象。
  2. 從代碼中的任何地方添加事件處理程序:將REF1對象中的任何事件(例如EVNT1)鏈接到PRC1過程。
  3. 刪除REF1(在VB中將其設置爲null或Nothing)。從這一刻起,您沒有提到您在步驟1中創建的對象。
  4. 但是,由於對象是邏輯的,因此它保留在內存中:它具有代碼(PRC1),該事件在事件觸發時執行(EVNT1)。

雖然我不給你任何建議如何釋放你的記憶在這種情況下,我希望這個描述將幫助你設計更好的架構和避免內存泄漏。

0

事件成爲內存(和CPU時間!)泄漏的最常見模式是當對象向另一個對象發出通知已發生事件時發生,以便它可以更新一些僅對短時間內感興趣的信息,活的物體。如果事件訂閱繼續存在,即使所有關心它的對象都已被放棄,只要進行通知的對象繼續存在,內存就會被浪費,並且每當對象執行通知時CPU時間將被浪費。如果無限數量的這種事件訂閱可以被創建和放棄,它們將構成無限的內存泄漏。

+0

嗨,一般內存泄漏不會發生簡單的事件使用。具體的糟糕設計會導致內存泄漏。不是事件本身。在之前的答案中,您可以看到糟糕的設計情況。 – Dima 2012-05-05 10:16:54