プログラムが予期せずに終了した場合(例外またはプロセスが終了した場合)はどうなりますか?このような(またはその他の)プログラムが終了するが、IDisposable
オブジェクトが適切に破棄されない状況はありますか?
私が質問している理由は、ペリフェラルと通信するコードを書いているためであり、それが悪い状態に置かれる可能性がないことを確認したいのです。
原因が例外であり、using
ブロックまたはtry catch finally
ブロック内からスローされた場合、必要に応じて破棄されます。 using
ブロックでキャッチされない場合、自動的に破棄されません(アプリケーションが適切に終了したときに行われないように)。
サンプル:
IDisposable d1 = new X();
using (IDisposable d2 = new X())
{
throw new NotImplementedException();
}
d1.Dispose();
d1
は破棄されませんが、d2
は通常は破棄されます。例外の種類によっては、using
ブロックの処理が妨げられ、プログラムがクラッシュする場合もあります。原因が電源障害またはシステムクラッシュである場合、もちろん、どちらも行うことができません。
プログラムが予期せず終了した場合(たとえば、プロセスを強制終了した場合)、 IDisposable.Dispose
メソッドが呼び出されます。そのようなイベントでは、これに依存しない方がよいでしょう。 Disposeメソッドは、コードによって手動で呼び出す必要があります。CLRが自動的に呼び出すものではありません。
Patrick HofmanとAlexeiの回答に加えて、アプリケーションが正常に終了した場合でもクリーンアップが実行されない場合があります。
ご存知のように、ガベージコレクターがDispose
インターフェースを実装するオブジェクトを収集するときに、IDisposable
メソッドは呼び出されません。ただし、GCは、ファイナライザとも呼ばれるFinalize
メソッドを呼び出します。その中で Dispose Pattern を使用してクリーンアップロジックを記述する必要があります。そして、はい、.Net Frameworkはすべてのファイナライザを実行しようとしますが、それらが実行されるという保証はありません。
例として、以下のプログラムには非常に長時間実行されるファイナライザがあります。したがって、.Netはプロセスを終了し、メッセージは表示されません。
class FinalizableObject
{
~FinalizableObject()
{
Thread.Sleep(50000);
Console.WriteLine("Finalized");
}
}
class Program
{
static void Main(string[] args)
{
new FinalizableObject();
}
}
これは、ネットワークハンドルの解放など、長時間かかる操作が原因で発生する可能性があります。
したがって、ファイナライザと使い捨てオブジェクトに依存しないでください。ただし、カーネルオブジェクトに対して開いているハンドルはすべて自動的に閉じられるので、心配する必要はありません。
回答に加えて、ファイナライザとGCに関するいくつかの興味深い記事を読むことをお勧めします。
コンソールアプリケーションを使用した非常に簡単なテストは、Disposeがプロセスの終了時に呼び出されないことを示しています。
_class DisposableTest : IDisposable
{
public void Dispose()
{
Console.WriteLine("Dispose called");
}
}
...
using (DisposableTest sw = new DisposableTest())
{
Thread.Sleep(20000);
}
_
タスクマネージャでプロセスを強制終了しても、Disposable.Dispose()
メソッドはトリガーされません。 20秒待つことになります。
したがって、すでに述べたように、アプリケーションがクラッシュしたり強制終了したりしたときに、使い捨てオブジェクトに依存しないでください。ただし、例外によってトリガーされる必要があります。 StackOverflowException
やOutOfMemoryException
のような例外が常にDispose()をトリガーするかどうか疑問に思っています。
[編集]
ちょうど私の好奇心をテストしました:
StackOverflowException
はプロセスを終了するため、Dispose()は呼び出されませんOutOfMemoryException
はDispose()の通常の呼び出しを許可しますはい、そのような状況があります。たとえば、TerminateProcess
を呼び出す、Environment.FailFast
を呼び出す、または内部CLRエラーが発生すると、追加のコードを実行せずにプロセスが終了します。そのような状況では、あなたができる最善のことは「まあ」と言うことです。
プロセスが予期せず終了しない場合でも、Dispose
の呼び出しは手動で行う必要があります。 Dispose
を呼び出すファイナライザを実装するオブジェクトがガベージコレクションされる場合を除いて、ランタイムを通じて行われるものではありません。したがって、ディスポーザブルをusing
でラップすることを忘れたり、オブジェクトを存続させるメモリリークを引き起こしたりすることは、Dispose
が呼び出されないこともあります。
信頼できるクリーンアップは、プロセスの終了時にオペレーティングシステムによってのみ実行されます。システムオブジェクトへの開いているハンドルはすべて閉じられます。最後のハンドルが閉じられると、OSまたはドライバーに実装されたクリーンアップが行われます。このクリーンアップコードがドライバーの一部ではないが、ユーザープロセスによって呼び出されることになっている場合は、コードをできるだけ堅牢にするか、クリーンアップを処理するウォッチドッグプロセスを実装するだけです。
IDisposableは単なるインターフェースです。それらの処理方法については、特別なことは何もありません。 IDisposableでDisposeを呼び出すと(明示的またはusingブロックを介して)、Disposeメソッドの内容が呼び出されます。他のオブジェクトと同じようにガベージコレクションされます。
インターフェースの目的は、実装者が明示的にクリーンアップする必要があるマネージリソースまたはアンマネージリソースを持つ可能性がある型のクリーンアップを定義できるようにすることです。
これらのリソースがすべて管理されている場合、ガベージコレクションで十分であり、実装は最適化のためだけである可能性があります。
それらが管理されていないか、管理されていないリソースへの接続がある場合、ガベージコレクションはおそらく十分ではありません。これが、IDisposableの完全に推奨される実装に、明示的な破棄とランタイムによる(ファイナライザを介した)破棄の両方の処理が含まれる理由です。
プロセスのシャットダウンはDisposeを呼び出さず、ファイナライザーの実行は保証されていません...そのため、プロセスを破棄するだけで十分であることを期待する必要があります。