catch ブロックがないため、finally が実行される保証はありません。MSDNから- try-finally (C# リファレンス)および「ロックと例外が混在しない」(Eric Lippert)
処理された例外内で、関連する finally ブロックが実行されることが保証されます。ただし、例外が未処理の場合、finally ブロックの実行は、例外のアンワインド操作がどのようにトリガーされるかに依存します。これは、コンピューターのセットアップ方法によって異なります。
そして、言及されているリンクから(CLRでの未処理の例外処理)、スレッドが終了することを意味するさまざまな考慮事項があります。しかし、それがロックオブジェクトをロックしたままにするかどうかは正直わかりません。
次のことを確認したい場合:
- ロックを解除します。しかし
- このレベルで例外を処理したくない代わりに、より高いレベルの例外ハンドラーで処理したい
次に、次のようにします。
TheXmlType xml = null;
Monitor.Enter(Lock);
bool inLock = true;
try {
...
xml = filter.Xml; // put this here in case it throws an exception
inLock = false; // set this here in case monitor.exit is to
// throw an exception so we don't do the same all over again in the catch block
Monitor.Exit(Lock);
return xml; // this is fine here, but i would normally put it outside my try
}
catch (Exception) {
if (inLock) Monitor.Exit(Lock);
throw;
}
ただし、catch (Exception)
例外を非表示にするために使用しないでください。これは、例外を再スローする場合にのみ問題ありません。また、単一のreturn
ステートメントを使用することをお勧めしますが、通常、これは try ブロックの外にあります。
編集:
テスト プログラムとMSDN - Exceptions in Managed Threadsから確認済み
.NET Framework バージョン 2.0 以降では、共通言語ランタイムにより、スレッド内の未処理の例外のほとんどが自然に処理されます。ほとんどの場合、これは未処理の例外が原因でアプリケーションが終了することを意味します。
そのため、例外を処理しないと、アプリケーションがクラッシュします (ロックについて心配する必要はありません)。それを処理すると、元のコードが実行され、最終的にブロックされ、問題ありません。
EDIT 2:最終的に非発射を適切に示していなかったため、テストコードを更新しました:
class Program
{
static void Main(string[] args) {
Program p =new Program();
p.Start();
Console.WriteLine("done, press enter to finish");
Console.ReadLine();
}
private readonly object SyncRoot = new object();
ManualResetEvent mre = new ManualResetEvent(false);
private void Start() {
/*
* The application will run the thread, which throws an exception
* While Windows kicks in to deal with it and terminate the app, we still get
* a couple of "Failed to lock" messages
* */
Thread t1 = new Thread(SetLockAndTerminate);
t1.Start();
mre.WaitOne();
for (int i = 0; i < 10; i++) {
if (!Monitor.TryEnter(this.SyncRoot, 1000)) {
Console.WriteLine("Failed to lock");
}
else {
Console.WriteLine("lock succeeded");
return;
}
}
Console.WriteLine("FINALLY NOT CALLED");
}
public int CauseAnOverflow(int i)
{
return CauseAnOverflow(i + 1);
}
public void SetLockAndTerminate() {
Monitor.Enter(this.SyncRoot);
Console.WriteLine("Entered");
try {
mre.Set();
CauseAnOverflow(1); // Cause a stack overflow, prevents finally firing
}
finally {
Console.WriteLine("Exiting");
Monitor.Exit(this.SyncRoot);
}
}
}