web-dev-qa-db-ja.com

ペンオブジェクトでDisposeを呼び出さないとどうなりますか?

このコードスニペットのDisposeオブジェクトでpenを呼び出さないとどうなりますか?

private void panel_Paint(object sender, PaintEventArgs e)
{
    var pen = Pen(Color.White, 1);
    //Do some drawing
}
38
Andreas Brinck

Penは、Disposeを呼び出すかどうかに関係なく、将来の不確定な時点でGCによって収集されます。

ただし、ペンによって保持されている管理されていないリソース(GDI +ハンドルなど)は、GCによってクリーンアップされません。 GCは、管理対象リソースのみをクリーンアップします。 Pen.Disposeを呼び出すと、これらの管理されていないリソースがタイムリーにクリーンアップされ、リソースがリークされていないことを確認できます。

ここで、Penにファイナライザーがあり、そのファイナライザーがアンマネージリソースをクリーンアップする場合、Penがガベージコレクションされると、アンマネージリソースがクリーンアップされます。しかし、要点は次のとおりです。

  1. 管理されていないリソースを解放するために、Disposeを明示的に呼び出す必要があります。
  2. ファイナライザーがあり、それが管理されていないリソースをクリーンアップするかどうかの実装の詳細について心配する必要はありません。

PenIDisposableを実装します。 IDisposableは、管理されていないリソースを破棄するためのものです。これは.NETのパターンです。

このトピックに関する以前のコメントについては、これを参照してください answer

26
jason

ここでいくつかの修正を行う必要があります。

Phil Devaneyからの回答について:

「... Disposeを呼び出すと、確定的なクリーンアップを実行できるため、強くお勧めします。」

実際、Dispose()を呼び出しても、.NETでGCコレクションが決定論的に発生するわけではありません。つまり、Dispose()を呼び出したからといってすぐにGCがトリガーされるわけではありません。次のGC(オブジェクトが存在するジェネレーション)中にオブジェクトをクリーンアップできることをGCに間接的に通知するだけです。つまり、オブジェクトがGen 1に存在する場合、Gen1コレクションが実行されるまでオブジェクトは破棄されません。プログラム的かつ決定論的にGCにコレクションを実行させることができる唯一の方法の1つ(唯一ではありませんが)は、GC.Collect()を呼び出すことです。ただし、GCは、アプリの実行時にメモリ割り当てに関するメトリックを収集することにより、実行時にそれ自体を「調整」するため、そうすることはお勧めしません。 GC.Collect()を呼び出すと、これらのメトリックがダンプされ、GCが「チューニング」を最初からやり直します。

答えについて:

IDisposableは、管理されていないリソースを破棄するためのものです。これは.NETのパターンです。

これは不完全です。 GCは非決定論的であるため、Disposeパターン( Disposeパターンを適切に実装する方法 )を使用して、使用しているリソース(マネージドまたはアンマネージド)を解放できます。解放しているリソースの種類とは関係ありません。ファイナライザーを実装する必要性は、使用しているリソースの種類と関係があります。つまり、ファイナライズできない(つまりネイティブの)リソースがある場合にのみ実装します。多分あなたは2つを混同しています。ところで、代わりにSafeHandleクラスを使用してファイナライザーを実装することは避けてください。このクラスは、P/InvokeまたはCOM相互運用機能を介してマーシャリングされるネイティブリソースをラップします。最終的にファイナライザーを実装する場合は、常にDisposeパターンを実装する必要があります。

私がまだ誰も言及していない重要な注意点の1つは、使い捨てオブジェクトが作成され、ファイナライザーが含まれている場合です(そして、ファイナライザーがあるかどうかはわかりません-そして、作成するべきではありませんそれに関する仮定)、それはファイナライズキューに直接送信され、少なくとも1つの追加のGCコレクションのために存続します。

GC.SuppressFinalize()が最終的に呼び出されない場合、オブジェクトのファイナライザーは次のGCで呼び出されます。 Disposeパターンを適切に実装するには、GC.SuppressFinalize()を呼び出す必要があることに注意してください。したがって、オブジェクトに対してDispose()を呼び出し、パターンが適切に実装されている場合、ファイナライザーの実行を回避できます。ファイナライザーを持つオブジェクトでDispose()を呼び出さない場合、オブジェクトのファイナライザーは次のコレクションのGCによって実行されます。なぜこれが悪いのですか? .NET4.6までのCLRのファイナライザスレッドはシングルスレッドです。このスレッドの負担を増やすとどうなるか想像してみてください。アプリのパフォーマンスはどこにあるかがわかります。

オブジェクトに対してDisposeを呼び出すと、次のことが可能になります。

  1. プロセスのGCへの負担を軽減します。
  2. アプリのメモリ圧力を減らします。
  3. lOH(Large Object Heap)が断片化され、オブジェクトがLOH上にある場合に、OutOfMemoryException(OOM)の可能性を減らします。
  4. ファイナライザーがある場合は、オブジェクトをファイナライズ可能およびf到達可能キューに入れないようにします。
  5. リソース(管理対象および非管理対象)がクリーンアップされていることを確認してください。

編集IDisposable (ここでは極端な皮肉)に関する「すべてを知っていて常に正しい」MSDNドキュメントが実際に行っていることに気づきましたいう

このインターフェースの主な用途は、管理されていないリソースを解放することです

誰もが知っているはずですが、MSDNは正しくなく、「ベストプラクティス」について言及したり示したりすることはなく、コンパイルされない例を提供することもあります。これがこれらの言葉で文書化されているのは残念です。しかし、私は彼らが何を言おうとしていたかを知っています。完璧な世界では、GCはあなたのためにすべての管理されたリソースをクリーンアップします(どれほど理想的か)。ただし、管理されていないリソースはクリーンアップされません。これは絶対に真実です。そうは言っても、人生は完璧ではなく、どんな用途でもありません。 GCは、rooted-referencesを持たないリソースのみをクリーンアップします。これは主に問題が存在する場所です。

.NETがメモリを「リーク」(または解放しない)できる約15〜20の異なる方法の中で、Dispose()を呼び出さない場合に最も噛み付く可能性が高いのは、イベントの登録解除/フック解除/ワイヤ解除/デタッチの失敗です。ハンドラー/委任。デリゲートがワイヤリングされているオブジェクトを作成し、その上でDispose()を呼び出さない(そしてデリゲートを自分でデタッチしない)場合でも、GCはオブジェクトにルート参照(つまりデリゲート)があると見なします。したがって、GCがそれを収集することはありません。

以下の@jorenのコメント/質問(私の返信はコメントするには長すぎます):

使用することをお勧めするDisposeパターンに関するブログ投稿があります ---(Disposeパターンを適切に実装する方法 )。参照を無効にする必要がある場合がありますが、そうしても問題はありません。実際、そうすることで、GCが実行される前に何かが実行されます。つまり、そのオブジェクトへのルート参照が削除されます。 GCは後で、ルート化された参照のコレクションをスキャンし、ルート化された参照を持たないものを収集します。この例を考えてみてください。「ClassA」タイプのインスタンスがあります。これを「X」と呼びましょう。 Xには「ClassB」タイプのオブジェクトが含まれています。これを「Y」と呼びましょう。 YはIDisposableを実装しているため、XはYを破棄するために同じことを行う必要があります。Xが第2世代またはLOHにあり、Yが第0世代または第1世代にあると仮定します。Dispose()がXで呼び出され、その実装がYへの参照の場合、Yへのルート参照はすぐに削除されます。 Gen0またはGen1でGCが発生した場合、Yのメモリ/リソースはクリーンアップされますが、XはGen 2またはLOHに存在するため、Xのメモリ/リソースはクリーンアップされません。

28
Dave Black

基になるGDI +ペンハンドルは、将来の不確定な時間、つまりPenオブジェクトがガベージコレクションされ、オブジェクトのファイナライザーが呼び出されるまで解放されません。これは、プロセスが終了するまで、またはそれよりも早くなる可能性がありますが、重要なのはその非決定論的です。 Disposeを呼び出すと、確定的なクリーンアップを実行できるため、強くお勧めします。

13
Phil Devaney

グラフィックオブジェクトでDisposeを呼び出さない場合の問題を本当に知りたい場合は、CLRプロファイラーを使用できます。CLRプロファイラーは無料でダウンロードできます ここ インストールフォルダー内(デフォルトはC:\CLRProfiler)はCLRProfiler.docであり、BrushオブジェクトでDisposeを呼び出さないとどうなるかを示す良い例があります。とても啓発的です。短いバージョンでは、グラフィックスオブジェクトは予想よりも多くのメモリを消費し、Disposeを呼び出さない限り、長時間ハングする可能性があります。オブジェクトが使用されなくなると、システムは最終的にオブジェクトをクリーンアップしますが、そのプロセスは、オブジェクトの処理が終了したときにDisposeを呼び出した場合よりも多くのCPU時間を消費します。 IDisposable here および here の使用についても読むことをお勧めします。

2
Bill W

使用中の.Netメモリの合計量は、.Net部分+使用中のすべての「外部」データです。 OSオブジェクト、開いているファイル、データベース、およびネットワーク接続はすべて、純粋に.Netオブジェクトではないいくつかのリソースを使用します。

グラフィックスはペンやその他のオブジェクトを使用しますが、これらは実際には「かなり」高価なOSオブジェクトです。 (ペンを1000x1000ビットマップファイルと交換できます)。これらのOSオブジェクトは、特定のクリーンアップ関数を呼び出すと、OSメモリから削除されます。 Pen関数とBitmapDispose関数は、呼び出したときにすぐにこれを実行します。

Disposeを呼び出さない場合、ガベージコレクターは「将来どこかで*」それらをクリーンアップするようになります。 (実際には、おそらくDispose()を呼び出すデストラクタ/ファイナライズコードを呼び出します)

*将来のどこかに無限メモリ(または1GB以上)を備えたマシンでは、非常に遠い将来になる可能性があります。何もしないマシンでは、その巨大なビットマップまたは非常に小さなペンをクリーンアップするのに30分以上かかることがあります。

2
CodingBarfield

ガベージコレクタがクリーンアップするまでリソースを保持します

1
m.edmondson

ファイナライザーを実装し、finalizeメソッドでDisposeを呼び出すかどうかによって異なります。その場合、ハンドルはGCで解放されます。

そうでない場合、ハンドルはプロセスが終了するまでそのままになります。

1
Aliostad

グラフィックのものでは、それは非常に悪い場合があります。

Windowsタスクマネージャーを開きます。 「列の選択」をクリックして、「GDIオブジェクト」という列を選択します。

特定のグラフィックオブジェクトを破棄しない場合、この数は増え続けます。

古いバージョンのWindowsでは、これによりアプリケーション全体がクラッシュする可能性があります(私が覚えている限り、制限は10000でした)。ただし、Vista/7についてはわかりませんが、それでも悪いことです。