13

ManualResetEventSlim:.Set()を呼び出した直後に.Reset()を呼び出しても、待機中のスレッドは解放されませ

(注:これはManualResetEvent、だけでなく、でも発生しManualResetEventSlimます。)

以下のコードをリリースモードとデバッグモードの両方で試しました。クアッドコアプロセッサで実行されているWindows764ビットで.Net4を使用して32ビットビルドとして実行しています。Visual Studio 2012からコンパイルしました(したがって、.Net 4.5がインストールされています)。

システムで実行したときの出力は次のとおりです。

Waiting for 20 threads to start
Thread 1 started.
Thread 2 started.
Thread 3 started.
Thread 4 started.
Thread 0 started.
Thread 7 started.
Thread 6 started.
Thread 5 started.
Thread 8 started.
Thread 9 started.
Thread 10 started.
Thread 11 started.
Thread 12 started.
Thread 13 started.
Thread 14 started.
Thread 15 started.
Thread 16 started.
Thread 17 started.
Thread 18 started.
Thread 19 started.
Threads all started. Setting signal now.

0/20 threads received the signal.

したがって、イベントを設定してすぐにリセットしても、単一のスレッドは解放されませんでした。Thread.Sleep()のコメントを外すと、それらはすべて解放されます。

これはやや予想外のようです。

誰か説明がありますか?

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    public static class Program
    {
        private static void Main(string[] args)
        {
            _startCounter = new CountdownEvent(NUM_THREADS); // Used to count #started threads.

            for (int i = 0; i < NUM_THREADS; ++i)
            {
                int id = i;
                Task.Factory.StartNew(() => test(id));
            }

            Console.WriteLine("Waiting for " + NUM_THREADS + " threads to start");
            _startCounter.Wait(); // Wait for all the threads to have called _startCounter.Signal() 
            Thread.Sleep(100); // Just a little extra delay. Not really needed.
            Console.WriteLine("Threads all started. Setting signal now.");
            _signal.Set();
            // Thread.Sleep(50); // With no sleep at all, NO threads receive the signal.
            _signal.Reset();
            Thread.Sleep(1000);
            Console.WriteLine("\n{0}/{1} threads received the signal.\n\n", _signalledCount, NUM_THREADS);
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

        private static void test(int id)
        {
            Console.WriteLine("Thread " + id + " started.");
            _startCounter.Signal();
            _signal.Wait();
            Interlocked.Increment(ref _signalledCount);
            Console.WriteLine("Task " + id + " received the signal.");
        }

        private const int NUM_THREADS = 20;

        private static readonly ManualResetEventSlim _signal = new ManualResetEventSlim();
        private static CountdownEvent _startCounter;
        private static int _signalledCount;
    }
}

注:この質問も同様の問題を引き起こしますが、答えがないようです(はい、これが発生する可能性があることを確認する以外は)。

ManualResetEventがすべての待機中のスレッドを一貫して解放しない問題


[編集]

Ian Griffithsが以下で指摘しているように、答えは、使用されている基盤となるWindowsAPIがこれをサポートするように設計されていないということです。

ManualResetEventSlim.Set()に関するMicrosoftのドキュメントに、誤って記載されているのは残念です。

イベントの状態をsignaledに設定します。これにより、イベントを待機している1つ以上のスレッドが続行できるようになります。

明らかに、「1つ以上」は「0以上」である必要があります。

4

1 に答える 1

10

aをリセットすることManualResetEventは、呼び出すMonitor.Pulseこととは異なります。特定の数のスレッドを解放することを保証するものではありません。それどころか、(基礎となるWin32同期プリミティブの)ドキュメントは、何が起こるかわからないことをかなり明確にしています。

オブジェクトの状態が通知されている間、任意の数の待機中のスレッド、またはその後に指定されたイベントオブジェクトの待機操作を開始するスレッドを解放できます。

ここでのキーフレーズは、ゼロを含む「任意の数」です。

Win32は提供しますPulseEventが、「この関数は信頼性が低く、使用しないでください」と書かれています。http://msdn.microsoft.com/en-us/library/windows/desktop/ms684914(v=vs.85).aspxにあるドキュメントのコメントは、パルススタイルのセマンティクスを確実に実現できない理由についての洞察を提供します。イベントオブジェクト。(基本的に、カーネルはイベントを待機しているスレッドを一時的に待機リストから削除することがあるため、スレッドがイベントの「パルス」を見逃す可能性が常にあります。これは、使用するPulseEvent場合でも、自分で実行しようとする場合でも同じです。イベントの設定とリセット。)

の意図されたセマンティクスManualResetEventは、それがゲートとして機能することです。ゲートは、設定すると開き、リセットすると閉じます。誰かがゲートを通過する前にゲートを開いてすぐに閉じた場合、誰もがまだゲートの反対側にいることに驚かないでください。ゲートを開いたままゲートを通過するのに十分な注意を払った人だけが通過します。それが機能することを意味しているので、あなたが見ているものを見ているのはそのためです。

特に、のセマンティクスは「オープンゲートであり、待機中のすべてのスレッドがゲートを通過することを確認する」というものでSetはありません。(つまり、カーネルがマルチオブジェクト待機で何をすべきかは明らかではありません。)したがって、イベントが現在のように使用されることを意図していないという意味で、これは「問題」ではありません。それを使おうとしているので、正しく機能しています。しかし、これを使って探している効果を得ることができないという意味で問題があります。(これは便利なプリミティブであり、あなたがやろうとしていることには役に立たないだけです。私はManualResetEvent、最初に閉じられ、一度だけ開かれ、二度と閉じられないゲートにのみ使用する傾向があります。)

したがって、おそらく他の同期プリミティブのいくつかを考慮する必要があります。

于 2013-02-25T13:59:18.847 に答える