C# で特定のスレッドで例外をスローするための良い方法はありますか
-
09-06-2019 - |
質問
書きたいコードはこんな感じです。
void MethodOnThreadA()
{
for (;;)
{
// Do stuff
if (ErrorConditionMet)
ThrowOnThread(threadB, new MyException(...));
}
}
void MethodOnThreadB()
{
try
{
for (;;)
{
// Do stuff
}
}
catch (MyException ex)
{
// Do the right thing for this exception.
}
}
スレッド A によってフラグが設定されているかどうかをスレッド B にスレッドセーフな方法で定期的にチェックさせることができることはわかっていますが、そうするとコードがより複雑になります。使用できるより良いメカニズムはありますか?
定期的なチェックのより具体的な例を次に示します。
Dictionary<Thread, Exception> exceptionDictionary = new Dictionary<Thread, Exception>();
void ThrowOnThread(Thread thread, Exception ex)
{
// the exception passed in is going to be handed off to another thread,
// so it needs to be thread safe.
lock (exceptionDictionary)
{
exceptionDictionary[thread] = ex;
}
}
void ExceptionCheck()
{
lock (exceptionDictionary)
{
Exception ex;
if (exceptionDictionary.TryGetValue(Thread.CurrentThread, out ex))
throw ex;
}
}
void MethodOnThreadA()
{
for (;;)
{
// Do stuff
if (ErrorConditionMet)
ThrowOnThread(threadB, new MyException(...));
}
}
void MethodOnThreadB()
{
try
{
for (;;)
{
// Do stuff
ExceptionCheck();
}
}
catch (MyException ex)
{
// Do the right thing for this exception.
}
}
解決
これは良い考えではありません
この記事では Ruby のタイムアウト ライブラリについて説明します。 これにより、スレッド間で例外がスローされます。
このようなことを行うことがいかに根本的に破綻しているかを説明しています。これは Ruby だけで壊れているわけではなく、スレッド間で例外をスローするあらゆる場所で壊れています。
一言で言えば、何が起こるか(実際に起こる)は次のとおりです。
スレッドA:
At some random time, throw an exception on thread B:
スレッドB:
try {
//do stuff
} finally {
CloseResourceOne();
// ThreadA's exception gets thrown NOW, in the middle
// of our finally block and resource two NEVER gets closed.
// Obviously this is BAD, and the only way to stop is to NOT throw
// exceptions across threads
CloseResourceTwo();
}
実際にはスレッド間で例外をスローしていないため、「定期チェック」の例は問題ありません。
「次にこのフラグを見たときに例外をスローする」というフラグを設定しているだけですが、「キャッチの途中でスローしたり、最終的にブロックしたりできる」という問題に悩まされないため、これは問題ありません。
ただし、それを行う場合は、単に「exitnow」フラグを設定し、それを使用することで、例外オブジェクトを作成する手間を省くことができます。そのためには、揮発性ブールがうまく機能します。
他のヒント
スレッドの中止など、他のメカニズムによってスレッドにスローされる可能性のある例外には十分な問題があるため、別の方法を見つける必要があります。
例外は、プロセスが処理できない例外的な事態を経験したことを通知するために使用されるメカニズムです。例外がそれを通知するために使用されるようなコードの記述は避ける必要があります。 何か他のもの 何か例外的なことを経験しました。
他のスレッドは例外を処理する方法を知らない可能性が高くなります。 コードによってスローされる可能性があるすべての場合.
つまり、スレッドを中止するには、例外を使用する以外のメカニズムを見つける必要があります。
イベント オブジェクトなどを使用して、スレッドに処理を中止するよう指示します。これが最良の方法です。
別の問題を調査しているときに、次の記事を見つけて、あなたの質問を思い出しました。
Rotor を使用して ThreadAbortException の深さを調べる
これは、.NET が Thread.Abort() を実装するために通過する循環を示しています。おそらく、他のクロススレッド例外も同様であるはずです。(やったー!)
オリオン・エドワーズが言っていることは完全に真実ではありません。それが「唯一の」方法ではありません。
// Obviously this is BAD, and the only way to stop is to NOT throw // exceptions across threads
CER の使用 (制約付き実行領域) C# では、リソースをアトミック操作として解放し、コードをスレッド間例外から保護できます。この手法は、Windows のネイティブ API で動作する .NET Framework のいくつかのクラスで使用されており、解放されていないハンドルによってメモリ リークが発生する可能性があります。
次の例は、 PrepareConstrainedRegions
方法。ハンドルを指定した既存のハンドルに確実に設定するには、ネイティブ ハンドルの割り当てとその後のそのハンドルの記録が確実に行われるようにする必要があります。 SafeHandle
オブジェクトはアトミックです。これらの操作の間に障害が発生すると (スレッドの中止やメモリ不足の例外など)、ネイティブ ハンドルがリークします。使用できます PrepareConstrainedRegions
ハンドルに漏れがないことを確認する方法。
単純な:
public MySafeHandle AllocateHandle()
{
// Allocate SafeHandle first to avoid failure later.
MySafeHandle sh = new MySafeHandle();
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally // this finally block is atomic an uninterruptible by inter-thread exceptions
{
MyStruct myStruct = new MyStruct();
NativeAllocateHandle(ref myStruct);
sh.SetHandle(myStruct.m_outputHandle);
}
return sh;
}
なぜこれをしたいのか知りたいです。それは良い習慣ではないので、それを行う簡単な方法はありません。おそらく、設計に立ち戻って、最終目標を達成するためのよりクリーンな方法を見つけ出す必要があります。
それは良い考えだとは思いません..この問題をもう一度解決してください。共有データなどの他のメカニズムを使用してスレッド間で信号を送信してみてください。
他のアイデアと同様に、それがそれほど良いアイデアかどうかはわかりませんが、本当にそれを実行したい場合は、ターゲットスレッドへのデリゲートの投稿と送信を可能にする SynchronizationContext のサブクラスを作成できます (WinForms スレッドの場合は、作業が完了します)。このようなサブクラスはすでに存在するため、自動的に実行されます)。ただし、ターゲット スレッドはデリゲートを受け取るために、ある種のメッセージ ポンプと同等の機能を実装する必要があります。
@オリオン・エドワーズ
finally ブロックで例外がスローされるという点については、私もそのとおりだと思います。
ただし、別のスレッドを使用して、この例外を割り込みとして使用する方法があると思います。
スレッド A:
At some random time, throw an exception on thread C:
スレッド B:
try {
Signal thread C that exceptions may be thrown
//do stuff, without needing to check exit conditions
Signal thread C that exceptions may no longer be thrown
}
catch {
// exception/interrupt occurred handle...
}
finally {
// ...and clean up
CloseResourceOne();
CloseResourceTwo();
}
スレッド C:
while(thread-B-wants-exceptions) {
try {
Thread.Sleep(1)
}
catch {
// exception was thrown...
if Thread B still wants to handle exceptions
throw-in-B
}
}
それともそれはただの愚かなことですか?