5

イベントがどのようにメモリ リークを引き起こすかを理解しようとしています。この stackoverflowの質問で適切な説明を見つけましたが、 Windgでオブジェクトを見ると、結果に混乱しています。まず、次のような単純なクラスがあります。

class Person
    {
        public string LastName { get; set; }
        public string FirstName { get; set; }

        public event EventHandler UponWakingUp;
        public Person()  {  }

        public void Wakeup()
        {
            Console.WriteLine("Waking up");
            if (UponWakingUp != null)
                UponWakingUp(null, EventArgs.Empty);
        }
    }

現在、このクラスを Windows フォーム アプリケーションで次のように使用しています。

public partial class Form1 : Form
    {
        Person John = new Person() { LastName = "Doe", FirstName = "John" };

        public Form1()
        {
            InitializeComponent();

            John.UponWakingUp += new EventHandler(John_UponWakingUp);
        }

        void John_UponWakingUp(object sender, EventArgs e)
        {
            Console.WriteLine("John is waking up");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            John = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            MessageBox.Show("done");
         }
    }

ご覧のとおり、Person クラスをインスタンス化し、UponWakingUp イベントをサブスクライブしました。このフォームにボタンがあります。ユーザーがこのボタンをクリックすると、イベントのサブスクライブを解除せずに、この Person インスタンスを null に設定します。次に、GC.Collect を呼び出して、ガベージ コレクションが実行されていることを確認します。ここにメッセージ ボックスを表示しているので、Windbg をアタッチして Form1 クラスによる参照のヘルプを参照できます。このクラス内では、そのイベントへの参照は表示されません (Form1 のデータが長すぎますが、Windbg の出力を以下に示します。私の質問に関連しています)。このクラスには Person クラスへの参照がありますが、null です。Form1 は Person クラスへの参照を持っていないため、イベントのサブスクライブを解除していないため、基本的にこれはメモリ リークのようには見えません。

私の質問は、これがメモリリークを引き起こすかどうかです。そうでない場合、なぜですか?

0:005> !do 0158d334   
Name:        WindowsFormsApplication1.Form1  
MethodTable: 00366390  
EEClass:     00361718  
Size:        332(0x14c) bytes  
File:        c:\Sandbox\\WindowsFormsApplication1\WindowsFormsApplication1\bin\Debug\WindowsFormsApplication1.exe  
Fields:  
      MT    Field   Offset                 Type VT     Attr    Value Name  
619af744  40001e0        4        System.Object  0 instance 00000000 __identity  
60fc6c58  40002c3        8 ...ponentModel.ISite  0 instance 00000000 site  
619af744  4001534      b80        System.Object  0   static 0158dad0 EVENT_MAXIMIZEDBOUNDSCHANGED  
**00366b70  4000001      13c ...plication1.Person  0 instance 00000000 John**  
60fc6c10  4000002      140 ...tModel.IContainer  0 instance 00000000 components  
6039aadc  4000003      144 ...dows.Forms.Button  0 instance 015ad06c button1  

0:008> !DumpHeap -mt 00366b70    
 Address       MT     Size  
total 0 objects  
Statistics:  
      MT    Count    TotalSize Class Name  
Total 0 objects  
4

2 に答える 2

6

これは循環参照の場合です。フォームには、 Johnフィールドを介してイベントをリッスンするオブジェクトへの参照があります。次に、John は、フォームのコンストラクターによってその OnWakingUp イベントがサブスクライブされたときに、フォームへの参照を取得します。

循環参照は、特定の自動メモリ管理スキーム、特に参照カウントで問題になる可能性があります。しかし、.NET ガベージ コレクターには問題はありません。form オブジェクトと Person オブジェクトのどちらにも追加の参照がない限り、両者の間の循環参照はお互いを存続させることはできません。

コードには、どちらにも追加の参照はありません。これにより、通常、両方のオブジェクトがガベージコレクションされます。ただし、フォーム クラスは特別です。それに対するネイティブの Windows ウィンドウが存在する限り、Winforms によって維持されるハンドルからオブジェクトへのテーブルに格納された内部参照によって、フォーム オブジェクトが存続します。それはジョンを生かし続けます。

したがって、これをクリーンアップする通常の方法は、ユーザーが右上隅の X をクリックしてウィンドウを閉じることです。これにより、ネイティブ ウィンドウ ハンドルが破棄されます。その内部テーブルからフォーム参照を削除します。次のガベージ コレクションでは、循環参照だけが表示され、両方が収集されます。

于 2013-01-20T20:44:26.890 に答える
4

答えは、実際には、リンクした質問への答えにあります。

リスナーがイベント リスナーをイベントにアタッチすると、ソース オブジェクトはリスナー オブジェクトへの参照を取得します。これは、イベント ハンドラーがデタッチされるか、ソース オブジェクトが収集されるまで、ガベージ コレクターによってリスナーを収集できないことを意味します。

ソースオブジェクト ( )を解放しているPersonので、リスナー(あなたのForm) は収集されても問題ありません。これが、メモリ リークがない理由です。

メモリ リークが発生するのは、この状況が IE の逆であり、イベント ソース (オブジェクト) が参照を保持しているにもかかわらFormず、イベントソース(オブジェクト) を破棄したい場合です。Person

于 2013-01-20T19:40:07.107 に答える