ガベージ コレクターは IDisposable.Dispose を呼び出しますか?
-
09-06-2019 - |
質問
ネット I使い捨てパターン 暗示する ファイナライザーを作成し、IDisposable を実装する場合、ファイナライザーは明示的に Dispose を呼び出す必要があることに注意してください。これは論理的であり、ファイナライザーが保証されるまれな状況で私が常に行ってきたことです。
ただし、これを実行するとどうなるでしょうか:
class Foo : IDisposable
{
public void Dispose(){ CloseSomeHandle(); }
}
ファイナライザーなどは実装しません。フレームワークは Dispose メソッドを呼び出しますか?
はい、これはばかげているように聞こえることはわかっていますし、すべての論理がそうではないことを暗示していますが、私は常に頭の片隅に2つのことがあり、確信が持てませんでした。
数年前、ある人が実際にこれを実現するだろうと私に言ったことがありますが、その人は「自分のことをよく知っている」という非常に確かな実績を持っていました。
コンパイラ/フレームワークは、実装するインターフェイスに応じて他の「魔法の」ことを実行します (例:foreach、拡張メソッド、属性に基づくシリアル化など)、これも「魔法」である可能性があることは当然です。
それについてたくさんの本を読んで、暗示されていることがたくさんありましたが、私は決して見つけることができませんでした。 決定的 この質問に対する答えは「はい」または「いいえ」です。
解決
.Net ガベージ コレクターは、ガベージ コレクションでオブジェクトの Object.Finalize メソッドを呼び出します。による デフォルト これはそうなります 何もない 追加のリソースを解放したい場合はオーバーライドする必要があります。
Dispose は自動的には呼び出されないため、必ず呼び出されます。 明示的に 「using」ブロックや「tryfinally」ブロック内など、リソースが解放される場合に呼び出されます。
見る http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx 詳細については
他のヒント
ブライアンのコメントは重要なので、この点を強調したいと思います。
ファイナライザーは、C++ のような決定論的なデストラクターではありません。他の人が指摘しているように、いつ呼び出されるかという保証はありません。実際、十分なメモリがあるかどうかは保証されません。 これまで 呼ばれる。
しかし、ファイナライザの悪い点は、ブライアンが言ったように、オブジェクトがガベージ コレクションを経ても生き残ってしまうことです。これは悪いことになる可能性があります。なぜ?
ご存じないかもしれませんが、GC は世代 0、1、2 とラージ オブジェクト ヒープの世代に分割されます。分割とは大まかな用語です。メモリの 1 ブロックを取得しますが、Gen 0 オブジェクトの開始位置と終了位置を示すポインターが存在します。
思考プロセスとしては、有効期間が短いオブジェクトを多数使用する可能性が高いということです。したがって、これらは GC が Gen 0 オブジェクトに簡単かつ高速にアクセスできるはずです。したがって、メモリ不足が発生した場合、最初に実行されるのは Gen 0 のコレクションです。
これで十分な圧力が解決されない場合は、戻って Gen 1 スイープ (Gen 0 をやり直し) を実行し、それでも不十分な場合は Gen 2 スイープ (Gen 1 と Gen 0 をやり直し) を実行します。そのため、長期間存続するオブジェクトのクリーンアップには時間がかかり、かなりのコストがかかる可能性があります (操作中にスレッドが中断される可能性があるため)。
これは、次のようなことを行う場合を意味します。
~MyClass() { }
あなたのオブジェクトは、何があっても第 2 世代まで存続します。これは、GC にはガベージ コレクション中にファイナライザーを呼び出す方法がないためです。そのため、ファイナライズする必要があるオブジェクトは特別なキューに移動され、別のスレッド (ファイナライザー スレッド - 強制終了すると、あらゆる種類の悪いことが発生します) によって消去されます。これは、オブジェクトがより長く滞留し、より多くのガベージ コレクションが強制される可能性があることを意味します。
つまり、これらはすべて、可能な限り IDisposable を使用してリソースをクリーンアップし、ファイナライザーの使用を回避する方法を真剣に模索する必要があるということを理解させるためです。それはあなたのアプリケーションにとって最善の利益です。
ここではすでにたくさんの良い議論があり、私はパーティーに少し遅れましたが、私自身もいくつかの点を追加したいと思いました。
- ガベージ コレクターが Dispose メソッドを直接実行することはありません。
- GC 意思 気が向いたときにファイナライザーを実行します。
- ファイナライザーを持つオブジェクトに使用される一般的なパターンの 1 つは、慣例により Dispose(bool disposing) として定義されているメソッドを呼び出して false を渡し、その呼び出しが明示的な Dispose 呼び出しではなくファイナライズによって行われたことを示すことです。
- これは、オブジェクトのファイナライズ中に他の管理オブジェクトについて何らかの仮定を行うのは安全ではないためです (オブジェクトはすでにファイナライズされている可能性があります)。
class SomeObject : IDisposable {
IntPtr _SomeNativeHandle;
FileStream _SomeFileStream;
// Something useful here
~ SomeObject() {
Dispose(false);
}
public void Dispose() {
Dispose(true);
}
protected virtual void Dispose(bool disposing) {
if(disposing) {
GC.SuppressFinalize(this);
//Because the object was explicitly disposed, there will be no need to
//run the finalizer. Suppressing it reduces pressure on the GC
//The managed reference to an IDisposable is disposed only if the
_SomeFileStream.Dispose();
}
//Regardless, clean up the native handle ourselves. Because it is simple a member
// of the current instance, the GC can't have done anything to it,
// and this is the onlyplace to safely clean up
if(IntPtr.Zero != _SomeNativeHandle) {
NativeMethods.CloseHandle(_SomeNativeHandle);
_SomeNativeHandle = IntPtr.Zero;
}
}
}
これは単純なバージョンですが、このパターンにはつまづきやすいニュアンスがたくさんあります。
- IDisposable.Dispose のコントラクトは、複数回呼び出しても安全でなければならないことを示しています (既に破棄されたオブジェクトに対して Dispose を呼び出しても何も行われません)。
- 使い捨てオブジェクトの継承階層を適切に管理することは、特にさまざまなレイヤーに新しい使い捨てリソースや管理対象外リソースが導入される場合、非常に複雑になる可能性があります。上記のパターンでは、Dispose(bool) は管理できるようにオーバーライドできるように仮想化されていますが、エラーが発生しやすいことがわかります。
私の意見では、使い捨ての参照と、ファイナライズが必要になる可能性のあるネイティブ リソースの両方を直接含む型を使用することは完全に避けた方がよいと考えています。SafeHandles は、内部で独自のファイナライゼーションを提供するネイティブ リソースを使い捨てにカプセル化することで、これを行うための非常にクリーンな方法を提供します (非同期例外によりネイティブ ハンドルが失われる可能性がある P/Invoke 中にウィンドウを削除するなど、他の多くの利点もあります)。 。
SafeHandle を定義するだけで、これは簡単になります。
private class SomeSafeHandle
: SafeHandleZeroOrMinusOneIsInvalid {
public SomeSafeHandle()
: base(true)
{ }
protected override bool ReleaseHandle()
{ return NativeMethods.CloseHandle(handle); }
}
含まれる型を次のように単純化できます。
class SomeObject : IDisposable {
SomeSafeHandle _SomeSafeHandle;
FileStream _SomeFileStream;
// Something useful here
public virtual void Dispose() {
_SomeSafeHandle.Dispose();
_SomeFileStream.Dispose();
}
}
私はそうは思わない。Dispose がいつ呼び出されるかを制御できます。つまり、理論的には、(たとえば) 他のオブジェクトの存在について仮定する廃棄コードを作成できることになります。ファイナライザーがいつ呼び出されるかを制御することはできないため、ファイナライザーが自動的に Dispose を自動的に呼び出すようにするかどうかは微妙です。
編集:念のため、行ってテストしてみました。
class Program
{
static void Main(string[] args)
{
Fred f = new Fred();
f = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Fred's gone, and he's not coming back...");
Console.ReadLine();
}
}
class Fred : IDisposable
{
~Fred()
{
Console.WriteLine("Being finalized");
}
void IDisposable.Dispose()
{
Console.WriteLine("Being Disposed");
}
}
あなたが説明する場合ではありませんが、GCは ファイナライザー 持っているなら、あなたのために。
しかし。次のガベージ コレクションでは、オブジェクトは収集されるのではなくファイナライズ キューに入り、すべてが収集されてからファイナライザーが呼び出されます。その後の次のコレクションでは解放されます。
アプリのメモリ負荷によっては、しばらくの間、そのオブジェクト生成用の gc が存在しない可能性があります。そのため、ファイル ストリームや DB 接続の場合、ファイナライザー呼び出しでアンマネージド リソースが解放されるまでしばらく待つ必要があり、問題が発生する可能性があります。
いいえ、呼ばれていません。
ただし、これにより、オブジェクトを破棄することを忘れないようにすることが簡単になります。ただ使用してください using
キーワード。
これについては次のテストを行いました。
class Program
{
static void Main(string[] args)
{
Foo foo = new Foo();
foo = null;
Console.WriteLine("foo is null");
GC.Collect();
Console.WriteLine("GC Called");
Console.ReadLine();
}
}
class Foo : IDisposable
{
public void Dispose()
{
Console.WriteLine("Disposed!");
}
GC は、 ない 破棄を呼び出します。それ 5月 ファイナライザーを呼び出してください。ただし、これさえもすべての状況で保証されるわけではありません。
これを参照してください 記事 これに対処する最善の方法について議論します。
に関するドキュメント I使い捨て 動作の非常に明確かつ詳細な説明とサンプルコードが示されています。GC は Dispose()
インターフェイス上のメソッドですが、オブジェクトのファイナライザーを呼び出します。
IDisposable パターンは主に開発者によって呼び出されるように作成されました。IDispose を実装するオブジェクトがある場合、開発者は次のいずれかを実装する必要があります。 using
オブジェクトのコンテキストにキーワードを追加するか、Dispose メソッドを直接呼び出します。
このパターンのフェイルセーフは、Dispose() メソッドを呼び出すファイナライザーを実装することです。そうしないと、次のようなメモリ リークが発生する可能性があります。何らかの COM ラッパーを作成し、System.Runtime.Interop.Marshall.ReleaseComObject(comObject) (Dispose メソッドに配置される) を決して呼び出さない場合。
clr には、ファイナライザーを含むオブジェクトを追跡し、GC によってそれらを Finalizer テーブルに格納し、GC によってクリーンアップ ヒューリスティックが開始されたときにそれらを呼び出すこと以外に、Dispose メソッドを自動的に呼び出す魔法はありません。