2

これは、ManualResetEvent を通知してすぐに閉じても安全ですか? と密接に関連しています。その問題に対する 1 つの解決策を提供する可能性があります。

同じ作業を実行したい可能性のあるスレッドがたくさんあるとしますが、それを実行できるのは 1 つだけで、他のスレッドはワーカーが完了するまで待機してその結果を使用する必要があります。

だから、基本的には一度だけ仕事をしたいのですか?

更新: これは、.net 4 の Lazy<T> を使用して対処できる初期化の問題ではないことを付け加えておきます。1 回とは、 task ごとに 1回という意味で、これらのタスクは実行時に決定されます。これは、以下の単純化された例からは明らかではないかもしれません。

前述の質問に対する Hans Passant の回答の簡単な例を少し変更すると、次のようにすれば安全だと思います。(今説明したユースケースとは少し異なりますが、スレッドとその関係に関しては同等です)

static void Main(string[] args)
{
    ManualResetEvent flag = new ManualResetEvent(false);
    object workResult = null;
    for (int ix = 0; ix < 10; ++ix)
    {
        ThreadPool.QueueUserWorkItem(s =>
        {
            try
            {
                flag.WaitOne();
                Console.WriteLine("Work Item Executed: {0}", workResult);
            }
            catch (ObjectDisposedException)
            {
                Console.WriteLine("Finished before WaitOne: {0}", workResult);
            }
        });
    }
    Thread.Sleep(1000);
    workResult = "asdf";
    flag.Set();
    flag.Close();
    Console.WriteLine("Finished");
}

私の質問の核心は次のとおりだと思います。

ObjectDisposedException が原因で中止された WaitOne の呼び出しは、メモリ バリアに関して、WaitOne の呼び出しが成功したことと同等ですか?

これにより、他のスレッドによる変数workResultへの安全なアクセスが保証されます。

私の推測: それは安全でなければなりません。そうでなければ、そもそも ManualResetEvent オブジェクトが閉じられたことを WaitOne が安全に判断するにはどうすればよいでしょうか?

4

3 に答える 3

3

ここに私が見るものがあります:

  • ObjectDisposedExceptionコードが次の競合状態を示しているため、 取得しています。
    • すべてのスレッドが flag.waitOne を呼び出す前に、flag.close を呼び出すことができます。

これをどのように処理するかは、flag.waitOne の後のコードの実行がどれほど重要かによって異なります。

1 つのアプローチを次に示します。

スピンアップされたすべてのスレッドが実際に実行される場合は、flag.close を呼び出す前に追加の同期を行うことができます。の代わりにStartNewonを使用すると、これを実現できます。タスクが完了するのを待つことができ、それから flag.close を呼び出します。これにより、競合状態が解消され、タスクを処理する必要がなくなります。Task.FactoryThread.QueueUserWorkItemObjectDisposedException

コードは次のようになります。

    static void Main(string[] args)
    {
        ManualResetEvent flag = new ManualResetEvent(false);
        object workResult = null;
        Task[] myTasks = new Task[10];
        for (int ix = 0; ix < myTasks.Length; ++ix)
        {
            myTasks[ix] = Task.Factory.StartNew(() =>
            {
                flag.WaitOne();
                Console.WriteLine("Work Item Executed: {0}", workResult);
            });
        }

        Thread.Sleep(1000);
        workResult = "asdf";
        flag.Set();
        Task.WaitAll(); // Eliminates race condition
        flag.Close();
        Console.WriteLine("Finished");
    }

上で見たように、Task は、あなたが見ている競合状態を排除する追加の同期を可能にします。

追加のメモとして、ManualResetEvent.waitOne はメモリ バリアを実行するため、workresult 変数は、それ以上のメモリ バリアや揮発性読み取りを必要とせずに更新された最新のものになります。

したがって、あなたの質問に答えるために、追加の同期を回避し、あなたのアプローチで ObjectDisposed 例外を処理する必要がある場合は、破棄されたオブジェクトがメモリバリアを実行していないと主張します。catchThread.MemoryBarrierブロックを呼び出して最新の値が読み取られていることを確認してください。

しかし、例外は高価であり、通常のプログラム実行で例外を回避できるのであれば、そうするのが賢明だと思います。

幸運を!

于 2012-02-17T10:36:29.447 に答える
0

単純化した例よりも少し複雑な、解決しなければならない実際の問題をもう少し検討して、Monitor.Wait と Monitor.PulseAll を使用することにしました。

Joe Albahari のC# での Threading は非常に有用であることが判明しました。この特定のケースでは、次のセクションが適用されます

于 2012-02-20T05:05:40.290 に答える
0

いくつかのポイント:

  1. これが .NET 4 の場合、これを行うにはLazyが適しています。

  2. メモリ バリアの観点から同等であるかどうかは、ほとんど無関係です。例外は、通常のコード パスの一部であってはなりません。これは予想される使用例ではないため、動作は未定義であると想定します。

于 2012-02-17T04:13:38.587 に答える