11

書きたいコードはこんな感じです。

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.
    }
}
4

8 に答える 8

10

これは良い考えではありません

この記事では、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」フラグを設定し、それを使用して、例外オブジェクトを作成する手間を省くことができます。そのためには、揮発性ブールがうまく機能します。

于 2008-09-04T20:55:16.097 に答える
10

スレッドの中止など、他のメカニズムによってスレッドにスローされる可能性のある例外には十分な問題があるため、別の方法を見つける必要があります。

例外とは、処理できない例外がプロセスに発生したことを通知するために使用されるメカニズムです。例外を使用して、他の何かが例外的なことを経験したことを通知するように、コードを記述しないようにする必要があります。

その他のスレッドは、コードによって例外がスローされる可能性があるすべてのケースで例外を処理する方法を知らない可能性が最も高いです。

つまり、例外を使用する以外に、スレッドを中止するための別のメカニズムを見つける必要があります。

イベント オブジェクトなどを使用して、スレッドに処理を中止するように指示します。これが最善の方法です。

于 2008-09-04T20:07:39.317 に答える
2

オリオン・エドワーズが言っていることは完全に真実ではありません: 「唯一の」方法ではありません.

// Obviously this is BAD, and the only way to stop is to NOT throw
// exceptions across threads

C# でCER ( Constrained Execution Regions ) を使用すると、リソースをアトミック操作として解放し、コードをスレッド間例外から保護できます。この手法は、Windows のネイティブ API で動作する .NET Framework のいくつかのクラスで使用されます。解放されていないハンドルがメモリ リークを引き起こす可能性があります。

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.runtimehelpers.prepareconstrainedregions.aspxを参照してください。

次の例は、メソッドを使用してハンドルを確実に設定する方法を示しています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;
}
于 2010-08-08T20:48:15.500 に答える
1

別の問題を調査しているときに、この記事に出くわし、あなたの質問を思い出しました。

Rotor を使用して ThreadAbortException の深さを配管する

これは、.NET が Thread.Abort() を実装するために通過する循環を示しています。おそらく、他のクロススレッド例外も同様である必要があります。(ええ!)

于 2008-09-06T01:14:04.000 に答える
0

@オリオン・エドワーズ

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
        }
    }

それともただのばかですか?

于 2008-09-04T21:34:59.467 に答える
0

なぜあなたがこれをやりたいのか知りたいです。それは良い習慣ではないので、簡単な方法はありません。おそらく、設計に戻って、最終目標を達成するためのよりクリーンな方法を見つけ出す必要があります。

于 2008-09-04T20:10:42.290 に答える
0

私はそれが良い考えだとは思わない.. この問題に別のクラックを入れる - 共有データのような他のメカニズムを使用してスレッド間で信号を送ってみてください。

于 2008-09-04T20:11:34.423 に答える
0

他のものと同様に、それが良いアイデアかどうかはわかりませんが、本当にやりたい場合は、SynchronizationContext のサブクラスを作成して、デリゲートをターゲット スレッドに投稿および送信できるようにすることができます (WinForms スレッドの場合は、そのようなサブクラスはすでに存在するため、自動的に行われます)。ただし、ターゲット スレッドは、デリゲートを受信するために、ある種のメッセージ ポンプに相当するものを実装する必要があります。

于 2008-09-04T20:38:11.990 に答える