3

アプリケーションでメモリ リークの問題が発生しました。次の簡単な例で問題の 1 つを再現することができました。

レプリケーションのセットアップ

1) オブジェクトの作成/破棄を追跡するために使用される次のヘルパー クラスを作成します。

public class TestObject
{
    public static int Count { get; set; }

    public TestObject()
    {
        Count++;
    }

    ~TestObject()
    {
        Count--;
    }
}

2) 3 つのボタンを持つ MDI フォームを作成します。最初のボタンは、次のように新しい MDI 子を作成します。

    private void ctlOpenMDI_Click(object sender, EventArgs e)
    {
        Form newForm = new Form();
        newForm.MdiParent = this;
        newForm.Tag = new TestObject();
        newForm.Show();
    }

2 番目のボタンを使用して同じことを行いますが、非 MDI 子フォームを使用します。

    private void ctlOpenNonMDIForm_Click(object sender, EventArgs e)
    {
        Form newForm = new Form();
        newForm.Tag = new TestObject();
        newForm.Show();
    }

3 番目のボタンはガベージ コレクションに使用され、ライブの TestObject インスタンスの数が表示されます。

    private void ctlCount_Click(object sender, EventArgs e)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();

        MessageBox.Show("Count: " + TestObject.Count);
    }

レプリケーションの手順

1) [MDI フォームを開く] ボタンをクリックし、MDI フォームを閉じてから、[カウント] ボタンをクリックします。Count: 1 が返されます。MDI 子フォームとそれが参照するオブジェクトは、ガベージ コレクションされませんでした。何かがまだそれへの参照を持っている必要があります。

また:

[MDI フォームを開く] を 3 回クリックし、3 つのフォームをすべて閉じてから、カウント ボタンをクリックします。Count: 1 が返されます。最後に閉じられた MDI 子フォームがガベージ コレクションされていないようです。

カウンターケース:

1) [非 MDI フォームを開く] をクリックして閉じます。次に、カウントボタンをクリックします。Count: 0 を返します。フォームとオブジェクトはガベージ コレクションされています。

回避策

これを行うことで、この問題を回避できます。

        Form form = new Form();
        form.MdiParent = this;
        form.Show();
        form.Close();

ガベージコレクションの前。これにより、このダミー フォームが最後に閉じられた MDI 子フォームになり、他のフォームをガベージ コレクションできるようになりますが、なぜこれを行う必要があるのでしょうか。何が起こっている?

また、フォームの開閉でちらつきが発生するため、少し見苦しく、かなりハッキーに見えます。

4

2 に答える 2

3

技術的には、それFormは「FormerlyActiveMdiChild」であるためです。これはバグのようです。幸いなことに、それほど深刻なものではありません。

収集されていないオブジェクトをトラブルシューティングする機能は、優れたスキルです。Debugging Tools for Windows ( http://www.microsoft.com/whdc/devtools/debugging/default.mspx )に付属している Microsoft の windbg デバッガーは、この目的に最適です。以下のウォークスルーでは、関係のない windbg からの多くの出力を削除したことに注意してください。

  1. type の MDI 子インスタンスを作成する代わりに、識別しやすいようにFormサブクラス化します。TestChildForm
  2. 実行可能ファイルを開始し、windbg をアタッチします。.NET 拡張機能をロードします!loadby sos mscorwks
  3. windbg で、 を実行し!dumpheap -type TestChildFormます。

     Address       MT     Size
    01e2e960 001c650c      320  
    
  4. 次に、実行し!gcroot 01e2e960ます。

    ESP:3de7fc:Root:01e29a78(System.EventHandler)->
    01e26504(WindowsFormsApplication1.Form1)->
    01e269b8(System.Windows.Forms.PropertyStore)->
    01e2ef04(System.Windows.Forms.PropertyStore+ObjectEntry[])
    
  5. 次に、 を実行!dumparray -details 01e2ef04して出力を検索します01e2e960

          MT    Field   Offset                 Type VT     Attr    Value Name
    6797ea24  40032a3       10         System.Int16  1 instance       56 Key
    6797ea24  40032a4       12         System.Int16  1 instance        1 Mask
    6798061c  40032a5        0        System.Object  0 instance 01e2e960 Value1
    
  6. 最後に、( によって決定される) に!name2ee System.Windows.Forms.dll System.Windows.Forms.Form続いて実行し、56 を探しました。!dumpclass 6604cb84!name2ee

          MT    Field   Offset                 Type VT     Attr    Value Name
    67982c4c  4001e80      fd8         System.Int32  1   static       56 PropFormerlyActiveMdiChild
    

Windbg の代わりに Visual Studio デバッガーを使用する場合は、最初に [プロパティ]、[デバッグ]、[アンマネージ コードのデバッグを有効にする] を有効にする必要があります。に代入.load sos.loadby sos mscorwksます。

于 2009-10-12T23:20:23.390 に答える
0

これが発生する理由は非常に単純で、このフォームへの参照がまだ存在します。良いニュースは、この参照を削除できることです。

フォームを閉じるイベントのイベント ハンドラーを追加します。

private void ctlOpenMDI_Click(object sender, EventArgs e)
{
    Form newForm = new Form();
    newForm.FormClosing += new FormClosingEventHandler(form_Closing);
    newForm.MdiParent = this;
    newForm.Tag = new TestObject();
    newForm.Show();
}

そして、イベントを処理するメソッド。

private void form_Closing(object sender, EventArgs e)
{
    Form form = sender as Form;
    form.MdiParent = null;
}

ここでは、MdiParent プロパティをリセットします。これにより、フォームは親の MdiChild リストから削除されます。フォームが閉じられると、この参照もリセットされます。

于 2010-03-18T13:42:35.790 に答える