web-dev-qa-db-ja.com

ロックされたオブジェクトは、内部で例外が発生してもロックされたままですか?

C#スレッドアプリで、オブジェクトをロックする場合、キューを言いましょう。例外が発生した場合、オブジェクトはロックされたままになりますか?擬似コードは次のとおりです。

int ii;
lock(MyQueue)
{
   MyClass LclClass = (MyClass)MyQueue.Dequeue();
   try
   {
      ii = int.parse(LclClass.SomeString);
   }
   catch
   {
     MessageBox.Show("Error parsing string");
   }
}

私はそれを理解しているように、キャッチの後のコードは実行されません-しかし、私はロックが解放されるかどうか疑問に思っていました。

75
Khadaji

最初; TryParseを検討しましたか?

in li;
if(int.TryParse(LclClass.SomeString, out li)) {
    // li is now assigned
} else {
    // input string is dodgy
}

ロックは2つの理由で解除されます。まず、lockは本質的に次のとおりです。

Monitor.Enter(lockObj);
try {
  // ...
} finally {
    Monitor.Exit(lockObj);
}

第二;内部例外をキャッチして再スローしないため、lockは実際に例外を認識しません。もちろん、MessageBoxの期間中はロックを保持していますが、これは問題になる可能性があります。

そのため、最も致命的な致命的な回復不能な例外を除くすべてでリリースされます。

81
Marc Gravell

この古い質問に対する回答で例外のロックを解除することは非常に危険なことです。はい、C#のロックステートメントには「最終的な」セマンティクスがあります。制御が正常または異常にロックを終了すると、ロックが解除されます。これは良いことのように話しているのですが、悪いことです!未処理の例外をスローするロックされた領域がある場合に行うべき正しいことは、より多くのユーザーデータを破壊する直前に病気のプロセスを終了するではなく、ロックを解放して続行します

このように見てください:ドアに鍵がかかっているバスルームと、外で待っている人が並んでいるとします。トイレの爆弾が出て、そこの人を殺します。あなたの質問は、「その状況では、次の人がトイレに入ることができるように、ロックが自動的に解除されますか?」です。はい、そうです。 それは良いことではありません。そこに爆弾が飛び出し、誰かを殺しました!配管はおそらく破壊され、家はもはや構造的に健全ではなく、そこに別の爆弾があるかもしれない。正しいことはできるだけ早く全員を退出し、家全体を破壊するです。

つまり、別のスレッドで変更されずにデータ構造から読み取るためにコードの領域をロックし、そのデータ構造内の何かが例外をスローした場合、データ構造が破損しているため]。ユーザーデータが台無しになりました。 破損データを保存しているため、この時点でユーザーデータを保存しようとするはしたくないでしょう。プロセスを終了するだけです。

別のスレッドが同時に状態を読み取らずにミューテーションを実行するためにコードの領域をロックし、ミューテーションがスローされた場合、データが以前に破損していなかった場合、今は確実ですこれは、ロックが想定されているシナリオですに対して保護。これで、その状態の読み取りを待機しているコードは、即時破損状態へのアクセス権を与えられ、おそらくそれ自体がクラッシュします。繰り返しますが、正しいことはプロセスを終了することです。

どのようにスライスしても、ロック内の例外は悪いニュースです。尋ねるべき正しい質問は、「例外が発生した場合に私のロックがクリーンアップされるか」ではありません。正しい質問は、「ロック内に例外が発生しないようにするにはどうすればよいでしょうか。また、ある場合、突然変異が以前の良好な状態にロールバックされるようにプログラムを構成するにはどうすればよいですか」です。

92
Eric Lippert

はい、それは適切にリリースされます。 locktry/finallyとして機能し、最後にMonitor.Exit(myLock)を持つため、どのように終了しても解放されます。補足として、catch(... e) {throw e;}は、eのスタックトレースを損傷するため、避けるのが最善です。それをキャッチしない方が良いですat、または代わりに:throw; のではなく throw e;これは再スローを行います。

本当に知りたい場合、C#4/.NET 4のロックは次のとおりです。

{
    bool haveLock = false;
    try {
       Monitor.Enter(myLock, ref haveLock);
    } finally {
       if(haveLock) Monitor.Exit(myLock);
    }
} 
38
Marc Gravell

「ロックステートメントはMonitor.Enterの呼び出しにコンパイルされ、次にtry…finallyブロックになります。finallyブロックでは、Monitor.Exitが呼び出されます。

X86とx64の両方のJITコード生成により、Monitor.Enter呼び出しとその直後のtryブロックの間でスレッドの中断が発生しないことが保証されます。

から取得: このサイト

13
GH.

ロックは適切に解除されます。 lockは次のように機能します。

try {
    Monitor.Enter(myLock);
    // ...
} finally {
    Monitor.Exit(myLock);
}

finallyブロックをどのように残しても、tryブロックは実行が保証されます。

5
Ry-

マークの優れた答えに少しだけ追加します。

このような状況は、lockキーワードが存在するまさにその理由です。これは、開発者がfinallyブロックでロックが解除されていることを確認するのに役立ちます。

Monitor.Enter/Exitの使用を強制されている場合タイムアウトをサポートするには、finallyブロックにMonitor.Exitへの呼び出しを配置し​​、例外が発生した場合にロックが適切に解放されるようにする必要があります。

4
Brian Rasmussen