ロックされたオブジェクトは、その内部で例外が発生した場合、ロックされたままになりますか?


87

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

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

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


1
最終的な考えとして(更新を参照)-おそらく、デキューの間だけロックを保持する必要があります...ロックの外側で処理を実行します。
MarcGravell

1
例外が処理されるため、catch後のコードは実行されます
cjk 2009

おかげで私はそれを逃したに違いありません、私はこの質問を削除する必要がありますか?
Vort3x 2012

4
サンプルコードはこの質問には適していないようですが、質問はかなり有効です。
SalvadorGomez

C#の設計者-ロック&例外
Jivan

回答:


91

最初; 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の期間中はロックを保持しているため、問題が発生する可能性があります。

したがって、最も致命的な壊滅的な回復不能な例外を除いて、すべてリリースされます。


15
私はtryparseを知っていますが、それは私の質問とは本当に関係がありません。これは質問を説明するための単純なコードでした-解析に関する真の懸念ではありません。で解析を交換してください任意のキャッチを強制し、あなたが快適になりますコード。
Khadaji 2009

13
新しいException( "説明のために");をスローするのはどうですか。; -p
MarcGravell

1
とのTheadAbortException間で発生する場合を除きます:blogs.msdn.com/ericlippert/archive/2009/03/06/…–Monitor.Entertry
Igal Tabachnik

2
ストリームを横断するような「致命的な壊滅的な回復不能な例外」。
Phil Cooper

99

この古い質問への回答の中で、例外時にロック解除することは非常に危険なことであると誰も言及していないことに注意してください。はい、C#のロックステートメントには「最終的に」セマンティクスがあります。コントロールが正常または異常にロックを終了すると、ロックが解除されます。皆さんはこれについて良いことのように話しているのですが、悪いことです!未処理の例外をスローするロックされた領域がある場合の正しい方法は、ロックを解除して続行するのではなく、より多くのユーザーデータを破棄する直前に、問題のあるプロセス終了することです。

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

つまり、考えてみてください。別のスレッドで変更せずにデータ構造から読み取るためにコードの領域をロックし、そのデータ構造内の何かが例外をスローした場合、それはデータ構造が原因である可能性が高いです。破損しています。ユーザーデータがめちゃくちゃになりました。破損したデータを保存するため、この時点ではユーザーデータの保存試みたくありません。プロセスを終了するだけです。

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

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


19
この問題は、IMOのロックとほぼ直交しています。予期される例外が発生した場合は、ロックを含むすべてをクリーンアップする必要があります。また、予期しない例外が発生した場合は、ロックの有無にかかわらず問題が発生します。
CodesInChaos 2012

10
上記の状況は一般論だと思います。時々例外は壊滅的な出来事を説明します。時々彼らはしません。コードでは、それぞれが異なる方法でそれらを使用します。例外が例外的であるが壊滅的ではないイベントのシグナルであることは完全に有効です-例外=壊滅的であると仮定すると、プロセス終了のケースはあまりにも具体的です。壊滅的な出来事である可能性があるという事実は、質問の有効性を損なうものではありません-同じ思考の流れにより、例外を処理できない可能性があります。その場合、プロセスは終了します...
Gerasimos R

1
@GerasimosR:確かに。強調すべき2つのポイント。まず、例外は、良性であると判断されるまで壊滅的であると想定する必要があります。次に、ロックされた領域から無害な例外がスローされた場合、ロックされた領域はおそらく不適切に設計されています。おそらくロック内であまりにも多くの作業を行っています。
Eric Lippert 2012年

1
敬意を表しますが、ボンネットの下でfinallyステートメントを採用しているロックブロックのC#デザインは悪いことだとリッパート氏が言っているようです。代わりに、ロック内でキャッチされなかったロックステートメントで例外が発生したすべてのアプリを完全にクラッシュさせる必要があります。それはあなたが言っていることではないかもしれませんが、もしそうなら、私はチームがあなたの(純粋主義者の理由で過激派!)ルートではなく、彼らが行ったルートを進んことを本当に嬉しく思います。すべての正当な敬意。
ニコラスピーターセン2016

2
構成不可能とはどういう意味かわからない場合:sとdの2つのリストを取り、sをロックし、dをロックし、sからアイテムを削除し、dにアイテムを追加し、ロックを解除するメソッド「転送」があるとします。 d、sのロックを解除します。この方法が正しいのは、他の誰かがYからXに転送しようとすると同時に、リストXからリストYに転送しようとする人がいない場合だけです。です。ロックはグローバル状態の安全でない変更であるため、転送方法が正しいため、より大きな問題に対する正しい解決策を構築することはできません安全に「転送」するには、プログラムのすべてのロックについて知っておく必要があります。
Eric Lippert 2016

43

はい、それは適切にリリースされます。最終的には/lockとして機能するため、どのように終了しても解放されます。補足として、;のスタックトレースに損傷を与えるため、避けるのが最善です。それはそれをキャッチし、より良いではない、すべての使用を:、または代替としてではなく再スローをしています。tryfinallyMonitor.Exit(myLock)catch(... e) {throw e;}ethrow;throw e;

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

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

14

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

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

取得元: このサイト


1
これが当てはまらないケースが少なくとも1つあります。4より前の.netバージョンでのデバッグモードでのスレッドアボート。理由は、C#コンパイラがMonitor.Enterとの間にNOPを挿入するtryため、の「直後に従う」条件JITに違反しています。
CodesInChaos 2012

6

ロックが正しく解除されます。Alockは次のように動作します。

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

また、finallyブロックをどのように離れても、tryブロックは実行されることが保証されています。


実際には、実行するコードは「保証」されていません(たとえば、電源ケーブルを引っ張ることができます)。これは、4.0でのロックの外観とはまったく
異なり

1
@MarcGravell:私はそれらの正確な2つのポイントについて2つの脚注を付けることを考えました。そして、私はそれがそれほど重要ではないと考えました:)
–Ry-

1
@MarcGravel:プログラマーが制御できるものではないので、誰もが常に「プラグを抜く」状況について話しているわけではないと思います:)
Vort3x 2012

5

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.