.NETのManualResetEventとAutoResetEventの違いは何ですか?


回答:


920

はい。料金所とドアの違いですね。ManualResetEvent手動で(リセット)を閉鎖する必要がドアです。これAutoResetEventは料金所で、1台の車が通り過ぎて次の車が通り抜ける前に自動的に閉まります。


166
それは素晴らしいアナロジーです。
twk '09 / 09/30

さらに悪いことに、AREをWaitOneに設定するのに長い時間待たないでください。そうしないと、その間にリセットされます。それで多くの放棄されたスレッドがありました。
Oliver Friedrich、

24
またはドアや回転式改札口のようなものです。
コンスタンティン

9
ああ、そういうわけで彼らは彼らが何であるかという名前が付けられています。
Arlen Beiler

1
@DanGoldsteinだけでなく、これが閉じていないと場合には他の誰かがそれを望んでいるので: msdn.microsoft.com/en-us/library/...
フィル・N DeBlanc

124

想像しているAutoResetEvent実行WaitOne()してReset()、単一のアトミック操作として。


16
ただし、WaitOneとResetをManualResetEventイベントで単一のアトミック操作として実行した場合でも、AutoResetEventとは異なる動作をします。ManualResetEventはすべての待機スレッドを同時に解放しますが、AutoResetEventは待機スレッドを1つだけ解放することを保証します。
マーティンブラウン

55

短い答えはイエスです。最も重要な違いは、AutoResetEventでは1つの待機中のスレッドのみが続行できることです。一方、ManualResetEventを使用すると、停止(リセット)するように指示するまで、スレッドをいくつか同時に許可します。


36

Joseph AlbahariによるC#3.0の簡単な本からの引用

C#でのスレッディング-無料の電子書籍

ManualResetEventはAutoResetEventのバリ​​エーションです。これは、WaitOne呼び出しでスレッドが通過した後、自動的にリセットされない点で異なり、ゲートのように機能します。リセットを呼び出すと、ゲートが閉じ、潜在的に、次に開くまで待機者のキューが蓄積されます。

ブールの「gateOpen」フィールド(volatileキーワードで宣言)を「spin-sleeping」と組み合わせてこの機能をシミュレートすることができます–フラグを繰り返しチェックしてから、短期間スリープします。

ManualResetEventsは、特定の操作が完了したこと、またはスレッドの初期化が完了して作業を実行する準備ができたことを通知するために使用されることがあります。


19

ManualResetEventvsの理解を明確にするために、簡単な例を作成しましたAutoResetEvent

AutoResetEvent:ワーカースレッドが3つあるとします。これらのスレッドのいずれかがWaitOne()他のすべての2つのスレッドを呼び出す場合、実行が停止され、シグナルを待ちます。私は彼らが使用していると仮定していますWaitOne()。それは似ています。私が働かなければ、誰も働かない。最初の例では、

autoReset.Set();
Thread.Sleep(1000);
autoReset.Set();

呼び出すと、Set()すべてのスレッドが機能し、シグナルを待ちます。1秒後、2番目の信号を送信し、実行して待機します(WaitOne())。これらの人はサッカーチームのプレーヤーであり、1人のプレーヤーがマネージャーが私に電話するまで待つと言ったら、他の人はマネージャーが彼らに続けるように言うまで待つ(Set()

public class AutoResetEventSample
{
    private AutoResetEvent autoReset = new AutoResetEvent(false);

    public void RunAll()
    {
        new Thread(Worker1).Start();
        new Thread(Worker2).Start();
        new Thread(Worker3).Start();
        autoReset.Set();
        Thread.Sleep(1000);
        autoReset.Set();
        Console.WriteLine("Main thread reached to end.");
    }

    public void Worker1()
    {
        Console.WriteLine("Entered in worker 1");
        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker1 is running {0}", i);
            Thread.Sleep(2000);
            autoReset.WaitOne();
        }
    }
    public void Worker2()
    {
        Console.WriteLine("Entered in worker 2");

        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker2 is running {0}", i);
            Thread.Sleep(2000);
            autoReset.WaitOne();
        }
    }
    public void Worker3()
    {
        Console.WriteLine("Entered in worker 3");

        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker3 is running {0}", i);
            Thread.Sleep(2000);
            autoReset.WaitOne();
        }
    }
}

この例Set()では、最初にヒットしたときにすべてのスレッドが解放され、1秒後にすべてのスレッドに待機するよう通知することがはっきりとわかります。WaitOne()内部で呼び出しているかどうかに関係なく、再度設定すると、手動で呼び出しReset()てすべてを停止する必要があるため、実行を続けます。

manualReset.Set();
Thread.Sleep(1000);
manualReset.Reset();
Console.WriteLine("Press to release all threads.");
Console.ReadLine();
manualReset.Set();

負傷しているプレーヤーに関係なくレフェリーとプレーヤーの関係が重要であり、他のプレーヤーがプレーするのを待って作業を続けます。レフリーが待機(Reset())と言った場合、すべてのプレーヤーは次の合図まで待機します。

public class ManualResetEventSample
{
    private ManualResetEvent manualReset = new ManualResetEvent(false);

    public void RunAll()
    {
        new Thread(Worker1).Start();
        new Thread(Worker2).Start();
        new Thread(Worker3).Start();
        manualReset.Set();
        Thread.Sleep(1000);
        manualReset.Reset();
        Console.WriteLine("Press to release all threads.");
        Console.ReadLine();
        manualReset.Set();
        Console.WriteLine("Main thread reached to end.");
    }

    public void Worker1()
    {
        Console.WriteLine("Entered in worker 1");
        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker1 is running {0}", i);
            Thread.Sleep(2000);
            manualReset.WaitOne();
        }
    }
    public void Worker2()
    {
        Console.WriteLine("Entered in worker 2");

        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker2 is running {0}", i);
            Thread.Sleep(2000);
            manualReset.WaitOne();
        }
    }
    public void Worker3()
    {
        Console.WriteLine("Entered in worker 3");

        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Worker3 is running {0}", i);
            Thread.Sleep(2000);
            manualReset.WaitOne();
        }
    }
}

13

autoResetEvent.WaitOne()

と類似しています

try
{
   manualResetEvent.WaitOne();
}
finally
{
   manualResetEvent.Reset();
}

アトミック操作として


これは概念的には正しいだけですが、実際的ではありません。WaitOneとResetの間でコンテキスト切り替えが発生する可能性があります。これは微妙なバグにつながる可能性があります。
hofingerandi 2015年

2
今、賛成投票できますか?ここでは2番目のコードブロックを実際に実行することはありません。違いを理解することが問題です。
vezenkov

11

はい、通常、同じスレッドに2つの回答を追加することはお勧めしませんが、別の方法で役立つ可能性があるため、以前の回答を編集/削除したくありませんでした。

今、私は以下のはるかに包括的で理解しやすいrun-to-learnコンソールアプリスニペットを作成しました。

2つの異なるコンソールで例を実行し、動作を観察するだけです。舞台裏で何が起こっているのか、より明確なアイデアが得られます。

手動リセットイベント

using System;
using System.Threading;

namespace ConsoleApplicationDotNetBasics.ThreadingExamples
{
    public class ManualResetEventSample
    {
        private readonly ManualResetEvent _manualReset = new ManualResetEvent(false);

        public void RunAll()
        {
            new Thread(Worker1).Start();
            new Thread(Worker2).Start();
            new Thread(Worker3).Start();
            Console.WriteLine("All Threads Scheduled to RUN!. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("Main Thread is waiting for 15 seconds, observe 3 thread behaviour. All threads run once and stopped. Why? Because they call WaitOne() internally. They will wait until signals arrive, down below.");
            Thread.Sleep(15000);
            Console.WriteLine("1- Main will call ManualResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _manualReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("2- Main will call ManualResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _manualReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("3- Main will call ManualResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _manualReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("4- Main will call ManualResetEvent.Reset() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _manualReset.Reset();
            Thread.Sleep(2000);
            Console.WriteLine("It ran one more time. Why? Even Reset Sets the state of the event to nonsignaled (false), causing threads to block, this will initial the state, and threads will run again until they WaitOne().");
            Thread.Sleep(10000);
            Console.WriteLine();
            Console.WriteLine("This will go so on. Everytime you call Set(), ManualResetEvent will let ALL threads to run. So if you want synchronization between them, consider using AutoReset event, or simply user TPL (Task Parallel Library).");
            Thread.Sleep(5000);
            Console.WriteLine("Main thread reached to end! ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);

        }

        public void Worker1()
        {
            for (int i = 1; i <= 10; i++)
            {
                Console.WriteLine("Worker1 is running {0}/10. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                // this gets blocked until _autoReset gets signal
                _manualReset.WaitOne();
            }
            Console.WriteLine("Worker1 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
        public void Worker2()
        {
            for (int i = 1; i <= 10; i++)
            {
                Console.WriteLine("Worker2 is running {0}/10. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                // this gets blocked until _autoReset gets signal
                _manualReset.WaitOne();
            }
            Console.WriteLine("Worker2 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
        public void Worker3()
        {
            for (int i = 1; i <= 10; i++)
            {
                Console.WriteLine("Worker3 is running {0}/10. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                // this gets blocked until _autoReset gets signal
                _manualReset.WaitOne();
            }
            Console.WriteLine("Worker3 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
    }

}

手動リセットイベント出力

自動リセットイベント

using System;
using System.Threading;

namespace ConsoleApplicationDotNetBasics.ThreadingExamples
{
    public class AutoResetEventSample
    {
        private readonly AutoResetEvent _autoReset = new AutoResetEvent(false);

        public void RunAll()
        {
            new Thread(Worker1).Start();
            new Thread(Worker2).Start();
            new Thread(Worker3).Start();
            Console.WriteLine("All Threads Scheduled to RUN!. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("Main Thread is waiting for 15 seconds, observe 3 thread behaviour. All threads run once and stopped. Why? Because they call WaitOne() internally. They will wait until signals arrive, down below.");
            Thread.Sleep(15000);
            Console.WriteLine("1- Main will call AutoResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _autoReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("2- Main will call AutoResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _autoReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("3- Main will call AutoResetEvent.Set() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _autoReset.Set();
            Thread.Sleep(2000);
            Console.WriteLine("4- Main will call AutoResetEvent.Reset() in 5 seconds, watch out!");
            Thread.Sleep(5000);
            _autoReset.Reset();
            Thread.Sleep(2000);
            Console.WriteLine("Nothing happened. Why? Becasuse Reset Sets the state of the event to nonsignaled, causing threads to block. Since they are already blocked, it will not affect anything.");
            Thread.Sleep(10000);
            Console.WriteLine("This will go so on. Everytime you call Set(), AutoResetEvent will let another thread to run. It will make it automatically, so you do not need to worry about thread running order, unless you want it manually!");
            Thread.Sleep(5000);
            Console.WriteLine("Main thread reached to end! ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);

        }

        public void Worker1()
        {
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine("Worker1 is running {0}/5. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(500);
                // this gets blocked until _autoReset gets signal
                _autoReset.WaitOne();
            }
            Console.WriteLine("Worker1 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
        public void Worker2()
        {
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine("Worker2 is running {0}/5. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(500);
                // this gets blocked until _autoReset gets signal
                _autoReset.WaitOne();
            }
            Console.WriteLine("Worker2 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
        public void Worker3()
        {
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine("Worker3 is running {0}/5. ThreadId: {1}.", i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(500);
                // this gets blocked until _autoReset gets signal
                _autoReset.WaitOne();
            }
            Console.WriteLine("Worker3 is DONE. ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
        }
    }

}

自動リセットイベント出力


これは、すべてを理解し、コードをコピーして、いくつかを変更しながらすべてを実行し、現在よく理解するための最良の方法でした
JohnChris

8

AutoResetEventは、ブール変数をメモリに保持します。ブール変数がfalseの場合はスレッドをブロックし、ブール変数がtrueの場合はスレッドのブロックを解除します。

AutoResetEventオブジェクトをインスタンス化するとき、ブール値のデフォルト値をコンストラクターに渡します。以下は、AutoResetEventオブジェクトをインスタンス化する構文です。

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

WaitOneメソッド

このメソッドは、現在のスレッドをブロックし、他のスレッドによるシグナルを待ちます。WaitOneメソッドは、現在のスレッドをスリープスレッド状態にします。WaitOneメソッドは、シグナルを受信した場合はtrueを返し、そうでない場合はfalseを返します。

autoResetEvent.WaitOne();

WaitOneメソッドの2番目のオーバーロードは、指定された秒数の間待機します。シグナルスレッドが取得されない場合は、処理が続行されます。

static void ThreadMethod()
{
    while(!autoResetEvent.WaitOne(TimeSpan.FromSeconds(2)))
    {
        Console.WriteLine("Continue");
        Thread.Sleep(TimeSpan.FromSeconds(1));
    }

    Console.WriteLine("Thread got signal");
}

2秒を引数として渡して、WaitOneメソッドを呼び出しました。whileループでは、信号を2秒間待ってから処理を続行します。スレッドがシグナルを取得すると、WaitOneはtrueを返し、ループを終了して、「スレッドがシグナルを取得」を出力します。

設定方法

AutoResetEvent Setメソッドは、作業を続行するために待機中のスレッドにシグナルを送信しました。以下は、Setメソッドを呼び出す構文です。

autoResetEvent.Set();

ManualResetEventは、ブール変数をメモリに保持します。boolean変数がfalseの場合はすべてのスレッドをブロックし、boolean変数がtrueの場合はすべてのスレッドのブロックを解除します。

ManualResetEventをインスタンス化するとき、デフォルトのブール値で初期化します。

ManualResetEvent manualResetEvent = new ManualResetEvent(false);

上記のコードでは、ManualResetEventをfalse値で初期化しています。つまり、WaitOneメソッドを呼び出すすべてのスレッドは、一部のスレッドがSet()メソッドを呼び出すまでブロックされます。

ManualResetEventをtrueの値で初期化すると、WaitOneメソッドを呼び出すすべてのスレッドがブロックされず、自由に先に進むことができません。

WaitOneメソッド

このメソッドは、現在のスレッドをブロックし、他のスレッドによるシグナルを待ちます。シグナルを受信した場合はtrueを返し、それ以外の場合はfalseを返します。

以下は、WaitOneメソッドを呼び出す構文です。

manualResetEvent.WaitOne();

WaitOneメソッドの2番目のオーバーロードでは、現在のスレッドがシグナルを待機するまでの時間間隔を指定できます。内部で時間内にシグナルを受信しない場合、falseを返し、メソッドの次の行に進みます。

以下は、時間間隔を指定してWaitOneメソッドを呼び出す構文です。

bool isSignalled = manualResetEvent.WaitOne(TimeSpan.FromSeconds(5));

WaitOneメソッドには5秒を指定しています。manualResetEventオブジェクトが5秒間信号を受信しない場合、isSignalled変数をfalseに設定します。

メソッドの設定

このメソッドは、待機中のすべてのスレッドにシグナルを送信するために使用されます。Set()メソッドは、ManualResetEventオブジェクトのブール変数をtrueに設定します。待機中のすべてのスレッドのブロックが解除され、さらに続行されます。

以下は、Set()メソッドを呼び出す構文です。

manualResetEvent.Set();

リセット方法

ManualResetEventオブジェクトでSet()メソッドを呼び出すと、そのブール値はtrueのままになります。値をリセットするには、Reset()メソッドを使用できます。Resetメソッドは、ブール値をfalseに変更します。

以下は、Resetメソッドを呼び出す構文です。

manualResetEvent.Reset();

シグナルを複数回スレッドに送信する場合は、Setメソッドを呼び出した直後にResetメソッドを呼び出す必要があります。


7

はい。これは完全に正しいです。

状態を示す方法としてManualResetEventを見ることができます。何かがオン(設定)またはオフ(リセット)です。一定期間の発生。その状態が発生するのを待機しているスレッドはすべて続行できます。

AutoResetEventは、信号に似ています。何かが起こったことを一発で示します。期間のないオカレンス。通常、発生した「何か」は小さく、単一のスレッドで処理する必要があるため、単一のスレッドがイベントを消費した後の自動リセット。


7

はい、そうです。

この二つの使い方でアイデアが出てきます。

いくつかの作業が終了し、これを待っている他の(スレッド)が続行できることを伝える必要がある場合は、ManualResetEventを使用する必要があります。

リソースへの相互排他的アクセスが必要な場合は、AutoResetEventを使用する必要があります。


1

AutoResetEventとManualResetEventを理解したい場合は、スレッドではなく割り込みを理解する必要があります。

.NETは、可能な限り最も低いレベルのプログラミングを思い起こさせたいと考えています。

割り込みは、低レベルのプログラミングで使用されるもので、低から高に(またはその逆)になった信号に相当します。これが発生すると、プログラムは通常の実行を中断し、このイベントを処理する関数に実行ポインタを移動します。

割り込みが発生したときに最初に行うことは、その状態をリセットすることです。ハードウェアは次のように機能します。

  1. ピンは信号に接続されており、ハードウェアはそれが変化するのをリッスンします(信号には2つの状態しかありません)。
  2. 信号が変化した場合は、何かが発生し、ハードウェアがメモリ変数を配置したことを意味しますを状態の発生に設定した(信号が再び変化した場合でも、この状態は維持されます)。
  3. プログラムは、変数が状態を変更し、実行を処理関数に移すことに気づきます。
  4. ここで、最初に行うべきことは、この割り込みを再度リッスンできるようにするために、このメモリ変数を発生していない状態にリセットすることです。

これがManualResetEventとAutoResetEventの違いです。
ManualResetEventが発生し、それをリセットしないと、次に発生したときに、それを聞くことができなくなります。

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