スレッドを同期できます。
EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
Task.Run(async () =>
{
    await Task.Delay(1000); // 時間のかかる処理
    waitHandle.Set();  // シグナル状態にする
    await Task.Delay(1000);
    waitHandle.Set();
});
waitHandle.WaitOne(); // シグナルを受け取るまで、スレッドはブロックされる
// EventResetModeがAutoResetのため、スレッドの解放後に自動で非シグナル状態となる
waitHandle.WaitOne(); // シグナルを受け取るまで、スレッドはブロックされる
		
public EventWaitHandle (
    bool initialState,                   // trueならば、初期状態でシグナル状態
    System.Threading.EventResetMode mode // イベントのリセットを、自動か手動のいずれで行うか
    );
			EventWaitHandle(Boolean, EventResetMode) - EventWaitHandle コンストラクター (System.Threading) | Microsoft Learn
			modeをAutoResetとするとAutoResetEvent、ManualResetとするとManualResetEventクラスに相当します。実際これらのクラスは、このコンストラクタをオーバーライドしているだけです。
[HostProtection(Synchronization=true, ExternalThreading=true)]
[System.Runtime.InteropServices.ComVisible(true)]
public sealed class AutoResetEvent : EventWaitHandle
{
    public AutoResetEvent(bool initialState) : base(initialState,EventResetMode.AutoReset){ }
}
			autoresetevent.cs
			AutoResetを指定すると、待機スレッドが解放された後にシグナル状態となったときに、自動的にリセットされます。ManualResetを指定すると、Reset()を呼ぶまでシグナル状態のままとなります。
EventWaitHandle waitHandle = new EventWaitHandle(true, EventResetMode.AutoReset); bool w1 = waitHandle.WaitOne(0); // true … 自動で非シグナル状態となる bool w2 = waitHandle.WaitOne(0); // false (非シグナル状態となっているため、シグナルを受け取れない) waitHandle.Reset(); bool w3 = waitHandle.WaitOne(0); // false bool w4 = waitHandle.WaitOne(0); // false
EventWaitHandle waitHandle = new EventWaitHandle(true, EventResetMode.ManualReset); bool w1 = waitHandle.WaitOne(0); // true … シグナル状態は変更されない bool w2 = waitHandle.WaitOne(0); // true (シグナル状態のままのため、シグナルを受け取れる) waitHandle.Reset(); bool w3 = waitHandle.WaitOne(0); // false bool w4 = waitHandle.WaitOne(0); // false
| 型 | プロパティ | |
|---|---|---|
| SafeWaitHandle | SafeWaitHandle | ネイティブのオペレーティングシステム ハンドル | 
| IntPtr | Handle | ネイティブのオペレーティングシステム ハンドル 互換性のために残されています。代わりにSafeWaitHandleを用います。 | 
現在のシグナル状態は、WaitOne(0)の戻り値で確認できます。
| メソッド | 機能 | 
|---|---|
| WaitOne(Int32, Boolean) | シグナル状態になるまで、現在のスレッドをブロックする | 
| Set() | シグナル状態にできる | 
| Reset() | 非シグナル状態にできる | 
| Close() | 保持されているすべてリソースを解放できる | 
このWaitHandleがシグナル状態になるか、タイムアウトするまで待機させられます。このメソッドは、Win32ではWaitForSingleObject()に相当します。
public virtual bool WaitOne (
    int millisecondsTimeout, // 待機するミリ秒数
    bool exitContext         // trueならば、同期されたコンテキスト内にいる場合は待機する前にコンテキストの同期ドメインを終了し、後で再取得する
    );
			WaitOne(Int32, Boolean) - WaitHandle.WaitOne Method (System.Threading) | Microsoft Learn
			millisecondsTimeoutに-1またはTimeout.Infiniteを指定すると、無限に待ちます。0を指定するとブロックせず、待機ハンドル (wait handle) の状態を確認し即座に返されます。省略すると-1となります。WaitOne - waithandle.cs
exitContextは、SynchronizationAttributeが指定されたContextBoundObjectクラスから派生したクラスで用いるような場合に影響します。省略するとfalseとなります。WaitOne exitContext
シグナルを受け取ったならばtrueが返されます。
EventWaitHandle waitHandle = new AutoResetEvent(false);
foo.SomeEvent += delegate
{
    // イベントが発生したならば、シグナル状態にする
    waitHandle.Set();
};
if (!waitHandle.WaitOne(1000))
{
    // 指定の時間内にシグナルを受け取らなかった
}
			複数のWaitHandleを対象とするならば、WaitHandle.WaitAll()やWaitHandle.WaitAny()を用います。
WaitAll()は、スレッド モデルがSTAでは使用できません。WaitAll(WaitHandle[]) - WaitHandle.WaitAll Method (System.Threading) | Microsoft Learn
[STAThread] static void Main() { EventWaitHandle waitHandle1 = new EventWaitHandle(true, EventResetMode.AutoReset); EventWaitHandle waitHandle2 = new EventWaitHandle(true, EventResetMode.AutoReset); WaitHandle.WaitAll(new[] { waitHandle1, waitHandle2 }); // NotSupportedException「STA スレッドでの複数のハンドルの WaitAll はサポートされていません。」 }
この場合、アプリケーションをMTAとできるならばMTAThread属性を指定します。それが不可能ならばMTAとしたスレッドを作成し、そこでWaitAll()を実行します。
bool result; Thread thread = new Thread(() => { result = WaitHandle.WaitAll(new[] { waitHandle1, waitHandle2 }); }); thread.SetApartmentState(ApartmentState.MTA); thread.Start(); thread.Join();c# - How do I use WaitHandler.WaitAll in MSTest without STA warnings? - Stack Overflow
シグナル状態にできます。
| AutoReset | ManualReset | 
|---|---|
| EventWaitHandle waitHandle = new EventWaitHandle(true, EventResetMode.AutoReset); bool w1 = waitHandle.WaitOne(0); // true bool w2 = waitHandle.WaitOne(0); // false bool r1 = waitHandle.Reset(); // true bool r2 = waitHandle.Reset(); // true (非シグナル状態で呼んでも問題ない) bool w3 = waitHandle.WaitOne(0); // false bool w4 = waitHandle.WaitOne(0); // false bool r3 = waitHandle.Set(); // true bool r4 = waitHandle.Set(); // true (シグナル状態で呼んでも問題ない) bool w5 = waitHandle.WaitOne(0); // true bool w6 = waitHandle.WaitOne(0); // false | EventWaitHandle waitHandle = new EventWaitHandle(true, EventResetMode.ManualReset); bool w1 = waitHandle.WaitOne(0); // true bool w2 = waitHandle.WaitOne(0); // true bool r1 = waitHandle.Reset(); // true bool r2 = waitHandle.Reset(); // true bool w3 = waitHandle.WaitOne(0); // false bool w4 = waitHandle.WaitOne(0); // false bool r3 = waitHandle.Set(); // true bool r4 = waitHandle.Set(); // true bool w5 = waitHandle.WaitOne(0); // true bool w6 = waitHandle.WaitOne(0); // true | 
イベントの状態を非シグナルに設定できます。これによりスレッドがブロックされるようにできます。
異なるスレッドから操作しても問題ありません。
EventWaitHandle waitHandle = new EventWaitHandle(true, EventResetMode.ManualReset);
bool r1 = waitHandle.Reset();
bool r2 = waitHandle.Set();
Task.Run(() =>
{
    bool r3 = waitHandle.Reset();
    bool r4 = waitHandle.Set();
}).Wait();
		このメソッドはDispose()からも呼ばれます。このメソッドの呼び出し後に他のメソッドを呼ぶと、「セーフ ハンドルは閉じられています。」としてObjectDisposedExceptionが投げられます。
ハンドルが閉じられていることは、SafeWaitHandle.IsClosedで確認できます。
EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset); bool r1 = waitHandle.SafeWaitHandle.IsClosed; // false waitHandle.Dispose(); bool r2 = waitHandle.SafeWaitHandle.IsClosed; // true