--- lang: ja-jp breaks: true --- # C# `ManualResetEvent.WaitOne` の `exitContext` の意味は?? 2021-10-17 ### WaitOne(Int32, Boolean) > WaitOne(Int32, Boolean) > > https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.waithandle.waitone?view=net-5.0#System_Threading_WaitHandle_WaitOne_System_Int32_System_Boolean_ > > exitContext Boolean > > 待機する前にコンテキストの同期ドメインを終了し (同期されたコンテキスト内にいる場合)、後で再取得する場合は、true。それ以外の場合は false。 意味がよくわなからない。。 ### コンテクストを抜ける際の注意点 > https://docs.microsoft.com/en-us/dotnet/api/system.threading.waithandle.waitone?view=net-5.0#System_Threading_WaitHandle_WaitOne_System_Int32_System_Boolean_ > > Notes on Exiting the Context > > `exitContext`パラメータは、`WaitOne`メソッドがデフォルトではないマネージドコンテキストの内部から呼び出されない限り、何の効果もありません。これは、スレッドが`ContextBoundObject`から派生したクラスのインスタンスの呼び出しの中にある場合に起こります。現在、Stringなどの`ContextBoundObject`から派生していないクラスのメソッドを実行していても、現在のアプリケーションドメインのスタック上に`ContextBoundObject`があれば、デフォルトではないコンテキストになる可能性があります。 > > コードが `nondefault` コンテキストで実行されている場合、`exitContext` に `true` を指定すると、スレッドは `WaitOne` メソッドを実行する前に `nondefault` マネージド コンテキストを終了します (つまり、デフォルト コンテキストに移行します)。スレッドは、`WaitOne` メソッドの呼び出しが完了すると、元のデフォルトではないコンテキストに戻ります。 > > これは、コンテキストにバインドされたクラスに `SynchronizationAttribute` がある場合に便利です。この場合、クラスのメンバーに対するすべての呼び出しが自動的に同期化され、同期化ドメインはクラスのコード本体全体になります。メンバのコールスタック内のコードが`WaitOne`メソッドを呼び出し、`exitContext`に`true`を指定すると、スレッドは同期ドメインを抜けるため、オブジェクトの任意のメンバへの呼び出しでブロックされているスレッドは処理を進めることができます。`WaitOne`メソッドが戻ると、呼び出しを行ったスレッドは同期ドメインに再び入るのを待たなければなりません。 ```csharp= using System; using System.Threading; using System.Runtime.Remoting.Contexts; [Synchronization(true)] public class SyncingClass : ContextBoundObject { private EventWaitHandle waitHandle; public SyncingClass() { waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); } public void Signal() { Console.WriteLine("Thread[{0:d4}]: Signalling...", Thread.CurrentThread.GetHashCode()); waitHandle.Set(); } public void DoWait(bool leaveContext) { bool signalled; waitHandle.Reset(); Console.WriteLine("Thread[{0:d4}]: Waiting...", Thread.CurrentThread.GetHashCode()); signalled = waitHandle.WaitOne(3000, leaveContext); if (signalled) { Console.WriteLine("Thread[{0:d4}]: Wait released!!!", Thread.CurrentThread.GetHashCode()); } else { Console.WriteLine("Thread[{0:d4}]: Wait timeout!!!", Thread.CurrentThread.GetHashCode()); } } } public class TestSyncDomainWait { public static void Main() { SyncingClass syncClass = new SyncingClass(); Thread runWaiter; Console.WriteLine("\nWait and signal INSIDE synchronization domain:\n"); runWaiter = new Thread(RunWaitKeepContext); runWaiter.Start(syncClass); Thread.Sleep(1000); Console.WriteLine("Thread[{0:d4}]: Signal...", Thread.CurrentThread.GetHashCode()); // This call to Signal will block until the timeout in DoWait expires. syncClass.Signal(); runWaiter.Join(); Console.WriteLine("\nWait and signal OUTSIDE synchronization domain:\n"); runWaiter = new Thread(RunWaitLeaveContext); runWaiter.Start(syncClass); Thread.Sleep(1000); Console.WriteLine("Thread[{0:d4}]: Signal...", Thread.CurrentThread.GetHashCode()); // This call to Signal is unblocked and will set the wait handle to // release the waiting thread. syncClass.Signal(); runWaiter.Join(); } public static void RunWaitKeepContext(object parm) { ((SyncingClass)parm).DoWait(false); } public static void RunWaitLeaveContext(object parm) { ((SyncingClass)parm).DoWait(true); } } // The output for the example program will be similar to the following: // // Wait and signal INSIDE synchronization domain: // // Thread[0004]: Waiting... // Thread[0001]: Signal... // Thread[0004]: Wait timeout!!! // Thread[0001]: Signalling... // // Wait and signal OUTSIDE synchronization domain: // // Thread[0006]: Waiting... // Thread[0001]: Signal... // Thread[0001]: Signalling... // Thread[0006]: Wait released!!! ``` ### WaitOne exitContext > WaitOne exitContext > > https://social.msdn.microsoft.com/Forums/en-US/ea5aea17-555c-4549-8d32-bbab14b35a77/waitone-exitcontext?forum=netfxbcl > CLRは、コンテキストという概念をサポートしています。 コンテキストとは、オブジェクトを論理的にまとめたものです。 コンテキストの内側にあるオブジェクトに対してメソッドの呼び出しが行われた場合、メソッド自体を除いて特に何も起こりません。 コンテキストの外にあるオブジェクトに対してメソッドが呼び出された場合、メソッド自体が実行される前に「何か」が起こる可能性があります。 `ContextBoundObject`から派生したすべての.NETクラスは、実行時にコンテキストと関連付けられます。 > > メソッドが呼び出される前に起こりうる「何か」の例としては、同期の実施が挙げられます。 `ContextBoundObject`から派生し、`[Synchronization]`属性を指定したオブジェクトは、ランタイムから自動的に同期サービスを受けることができます。 つまり、オブジェクトのコンテキスト内では、1つのスレッドしか実行できないことになります。 > > `WaitOne`メソッドの`exitContext`パラメータは、待機を発行する前に同期コンテキストを離れるかどうかを指定します。 これにより、リエントランシー`reentrancy`が可能になります。 これが必要となるシナリオは以下の通りです。 > > ```csharp= > Code Snippet > [Synchronization] > public class MyCounter : ContextBoundObject { > > private int _expectedCounterVal; > private int _currentCounterVal; > private ManualResetEvent _event = new > ManualResetEvent(false); > > public void WaitUntilCounterIs(int counterVal) { > _expectedCounterVal = counterVal; > _event.WaitOne(TimeSpan.FromDays(1), true); > } > > public void IncrementCounter() { > if (++_currentCounterVal >= _expectedCounterVal) { > _event.Set(); > } > } > } > ``` > > この場合、`WaitOne`メソッドの`exitContext=true`パラメータを指定せずに`WaitUntilCounterIs`メソッドを発行してしまうと、他のスレッドがそのオブジェクトの`IncrementCounter`メソッドを呼び出すことができなくなり、デッドロックになってしまいます。 しかし、`exitContext=true`が指定されていれば、`WaitUntilCounterIs`メソッドがまだ返ってきていなくても、他のスレッドがある時点で`IncrementCounter`メソッドに入り、イベントのシグナルを送ることができます。 なるほど。。。 ###### tags: `C#` `ManualResetEvent.WaitOne` `exitContext`