1

データベースから一連のレコードを抽出するデータ エクスポート プログラムに取り組んできました。手順の 1 つは、RTF テキスト文字列をプレーン テキストに変換することであり、実行時にユーザー オブジェクトのメモリ リークが発生しました。タスク マネージャーが表示する列の 1 つは「ユーザー オブジェクト」です。これが 10,000 に達すると、プログラムは割り当て領域を使い果たし、プログラムは「ウィンドウ ハンドルの作成エラー」でエラーになります。

これは、メソッドの最後でオブジェクトを破棄しなかったために発生していました。

私の質問は、なぜ C#/.net が私のためにそれを処分しなかったのですか?

リークを再現するコードの簡単なサンプルを次に示します。コードを Winforms アプリケーションに挿入し、ボタンを押して、メモリの浪費をループさせます。

private void wasteMemory()
{
    System.Windows.Forms.RichTextBox rtfBox = new System.Windows.Forms.RichTextBox();

    //RTF text that reads "Hello World"
    rtfBox.Rtf = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}}  {\\colortbl ;\\red0\\green0\\blue0;}  \\viewkind4\\uc1\\pard\\cf1\\fs29 Hello World} ";

    //If line below is commented out, User Objects grow out of control.
    //rtfBox.Dispose();
}

private void button1_Click(object sender, EventArgs e)
{
    for (int i = 1; i < 100000; i++)
    {            
        wasteMemory();
    }
}

メソッドのスコープ内で作成されたオブジェクトは、メソッドが完了すると破棄されるというのが私の理解です。rtfBox が破棄されることを期待していましたが、そうではありません。

4

7 に答える 7

6

Dispose メソッドは、ネイティブ リソースを持つオブジェクトにクリーンアップの機会を与える .NET の方法です。これは、C++ の Destructor/delete のようなものですが、実際にはそうではありません。IDisposable を実装するオブジェクトで Dispose を呼び出さない場合、これはバグであり、メモリ リークが発生する可能性が高くなります。次のようにするのが最善です。

using(System.Windows.Forms.RichTextBox rtfBox = new System.Windows.Forms.RichTextBox())
{

  //RTF text that reads "Hello World"
  rtfBox.Rtf = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}}  {\\colortbl ;\\red0\\green0\\blue0;}  \\viewkind4\\uc1\\pard\\cf1\\fs29 Hello World} ";
}

using ブロックは、期待どおりに動作します。これは、スタック上のオブジェクトに対する C++ のメソッド スコープのようなものと考えることができます。

メソッドのスコープ内で作成されたオブジェクトは、メソッドが完了すると破棄されるというのが私の理解です。rtfBox が破棄されることを期待していましたが、そうではありません。

いいえ、これはまったく当てはまりません。または、他のほとんどのガベージ コレクション言語では、その点については当てはまりません。ここでオブジェクトが動的に割り当てられていることを認識している場合 (つまり、ポインターに非常によく似ています)、動的に割り当てられたメモリはポインターがスコープ外になったときにクリーンアップされないため、C++ のような言語にも当てはまりません:明示的に削除を呼び出します。.NET では、オブジェクトがファイナライズされ、デストラクタが呼び出され、ガベージ コレクタがそれに近づくと dispose が呼び出されますが、それまでは呼び出されません。スコープ外に出ると、問題のオブジェクトが収集される資格があることをガベージ コレクターに通知するだけです。ただし、ネイティブ コード、ファイル ハンドル、またはその他の IDisposable 実装オブジェクトなど、リソースを持つものはすべて、経由で破棄されることになっています。

詳細については、http://msdn.microsoft.com/en-us/library/system.idisposable.aspxを参照してください。

于 2013-09-11T17:14:22.867 に答える
6

ここまでのすべての回答は不完全です。はい、アンマネージ リソースをクリーンアップする必要があるのは事実ですが、IDisposable を実装するクラスは既にそれを行っています。それはポイントではありません。

IDisposable を適切に実装するクラスでは、オブジェクトが明示的または暗黙的に破棄されない場合、ガベージ コレクションのファイナライザー ステージで破棄されます。ただし、このプロセスは、オブジェクトがスコープ外に出たときにすぐには行われません。gc の実行には数分から数時間かかる場合があります。

ここでの問題は、自分で Dispose() を呼び出さない場合 (または、Dispose() を using ステートメントでラップして暗黙的に呼び出す場合)、(クラスによって正しく実装されている場合) オブジェクトはガベージ コレクターまで破棄されないことです。これにはかなりの時間がかかる可能性があります。

これは、ガベージ コレクターが参照されていないオブジェクトを破棄する前に、管理されていないリソースを使い果たす可能性があることを意味します。そして、それはまさにあなたが直面している問題です。

Dispose() を自分で呼び出すと、アンマネージ オブジェクトは、GC が到達するたびにクリーンアップされるのではなく、処理が完了した直後に確実にクリーンアップされます。

図書館のようなものだと考えてください。誰かが本をチェックアウトすると、棚に 5 冊あります。他の人がこの図書館をチェックアウトすると、返却する人もいます..しかし、すぐに棚に置かれるわけではなく、誰かがそれらをチェックインして再棚に戻すまで、返却ビンに座っています.

Dispose を呼び出すことは、図書館員に本を渡して、すぐにチェックインしてもらい、次の人が取り出せるように棚に戻すようなものです。

于 2013-09-11T17:23:16.877 に答える
2

.NET は、メモリの自動ガベージ コレクションのみを提供します。ハンドルについては何も知らないので、自分でクリーンアップする必要があります。それが、IDisposableパターンとファイナライザーの目的です。

于 2013-09-11T17:15:26.230 に答える
1

.NET ランタイムとそのガベージ コレクターは、オブジェクトがマネージド オブジェクトである場合にのみオブジェクトを破棄します。管理されていないオブジェクトは、ユーザーが破棄する必要があります。これは、管理されていないオブジェクトが、ランタイムによって制御されていないリソースを利用できるためです。ランタイムがこれらのオブジェクト自体をオフにすると、システム メモリに大量のガベージが残り、他のプロセスでは処理できなくなります。

この質問を確認することをお勧めします: .NET の「管理された」リソースと「管理されていない」リソースの意味は何ですか?

于 2013-09-11T17:15:30.310 に答える
0

ガベージ コレクターは、メモリ不足にのみ応答します。GUI ハンドルなど、他の制約されたリソースがある場合は、それらを適切に処理していることを確認する必要があります。ガベージ コレクターは、それを処理するとは主張しません。

まさにそのIDisposableためです。

于 2013-09-11T17:15:48.617 に答える
0

コードで呼び出しているガベージ コレクションと dispose メソッドには違いがあります。ガベージ コレクターは、参照がなくなったオブジェクトを収集しますが、コレクションは .NET ランタイムのドメイン内にあります。オブジェクトに関連付けられたネイティブ リソースがある場合、ガベージ コレクターはそれらをクリーニングする方法がありません。

インターフェイスは、IDisposableこの問題に取り組むように設計されています。オブジェクトのユーザーは、メソッドを手動で呼び出して、オブジェクト自体 (たとえば、使用する可能性のあるネイティブ リソース) をクリーンアップする機会を与えることIDisposableが期待されます。Dispose

IDisposableオブジェクトの使用を容易にする C# の "using" ステートメントを見てみましょう。

さらに読む:

IDisposable: http://msdn.microsoft.com/en-us/library/system.idisposable.aspx

ステートメントの使用: http://msdn.microsoft.com/en-us/library/yh598w02.aspx

于 2013-09-11T17:16:40.347 に答える
0

.NET は、.NET が割り当てるメモリについてのみ認識します。これは、コードがメモリを割り当てる「アンマネージ」コードを呼び出した場合、.NETこのメモリが存在することを認識しないことを意味します。

これが .Dispose() が存在する理由であり、事前に「管理されていない」メモリを破棄できます。アンマネージ メモリを使用する .NET オブジェクトを作成する人は、IDisposable を実装する必要があります。

アンマネージ オブジェクトが正しく実装されている場合は、GC が大規模な片付けを行うたびに呼び出される「ファイナライザー」 ~ClassName() も含まれます。ただし、呼び出されるファイナライザーに依存するべきではありません。どちらかといえば、ファイナライザーは、プロセスが突然閉じられた場合にクリーンアップするため、または破棄パターンを理解していない開発者のための松葉杖として存在します。

于 2013-09-11T17:18:13.260 に答える