いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

.NET クラスライブラリ探訪-042 (System.Threading.ManualResetEventSlim)(待機ハンドル, 軽量版, CancelaltionToken, .NET 4.0)


今回は、ManualResetEventSlimクラスについて。
ManualResetEventSlimクラスは、.NET 4.0からSystem.Threading名前空間に追加されたクラスです。


元々、System.Threading.ManualResetEventクラスというのが存在していて
それの軽量版という位置づけになります。短い時間で待機する場合は
こちらを利用した方がパフォーマンスが良くなります。
(短い時間の待機の場合やイベントがプロセスをまたがらない場合、ManualResetEventSlimクラスはビジースピンを行う為。)


同じように追加されたクラスとして、SemaphoreSlimクラスというのもあります。
こちらも、Samaphoreクラスの軽量版として追加されています。


で、このManualResetEventSlimクラスですが、基本的に元となっているManualResetEventクラスと
機能的に変わりません。


ただし、以下のメソッドが追加されています。

Waitメソッドに、CancellationTokenを受け付けるオーバーロードが追加されている。


CancellationTokenは、.NET 4.0から追加されたキャンセル処理のための新たな仕組みです。
タスク並列ライブラリ (TPL) でよく利用されます。


キャンセルトークンを指定するWaitメソッドの場合、通常のシグナル状態になるまで待機すると同時に
キャンセルされたか否かも検知するようになります。
注意点として、キャンセルトークンを指定した場合、キャンセル操作が行われると
OperationCanceledExceptionが発生するので、try-catchが必須となります。


以下、サンプルです。

    #region ManualResetEventSlimSamples-01
    /// <summary>
    /// ManualResetEventSlimクラスについてのサンプルです。
    /// </summary>
    /// <remarks>
    /// ManualResetEventSlimクラスは、.NET 4.0で追加されたクラスです。
    /// 元々存在していたManualResetEventクラスよりも軽量なクラスとなっています。
    /// 特徴しては、以下の点が挙げられます。
    ///     ・WaitメソッドにCancellationTokenを受け付けるオーバーロードが存在する。
    ///     ・非常に短い時間の待機の場合、このクラスは待機ハンドルではなくビジースピンを利用して待機する。
    /// </remarks>
    public class ManualResetEventSlimSamples01 : IExecutable
    {
        public void Execute()
        {
            //
            // 通常の使い方.
            //
            ManualResetEventSlim mres = new ManualResetEventSlim(false);
            
            ThreadPool.QueueUserWorkItem(DoProc, mres);
            
            Console.Write("メインスレッド待機中・・・");
            mres.Wait();
            Console.WriteLine("終了");
            
            //
            // WaitメソッドにCancellationTokenを受け付けるオーバーロードを使用。
            //
            mres.Reset();
            
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            CancellationToken       token       = tokenSource.Token;
            
            Task task = Task.Factory.StartNew(DoProc, mres);
            
            //
            // キャンセル状態に設定.
            //
            tokenSource.Cancel();
            
            Console.Write("メインスレッド待機中・・・");

            try
            {
                //
                // CancellationTokenを指定して、Wait呼び出し。
                // この場合は、以下のどちらかの条件を満たした時点でWaitが解除される。
                //    ・別の場所にて、Setが呼ばれてシグナル状態となる。
                //    ・CancellationTokenがキャンセルされる。
                //
                // トークンがキャンセルされた場合、OperationCanceledExceptionが発生するので
                // CancellationTokenを指定するWaitを呼び出す場合は、try-catchが必須となる。
                //
                // 今回の例の場合は、予めCancellationTokenをキャンセルしているので
                // タスク処理でシグナル状態に設定されるよりも先に、キャンセル状態に設定される。
                // なので、実行結果には、「*** シグナル状態に設定 ***」という文言は出力されない。
                //
                mres.Wait(token);
            }
            catch (OperationCanceledException cancelEx)
            {
                Console.Write("*** {0} *** ", cancelEx.Message);
            }

            Console.WriteLine("終了");
        }
        
        void DoProc(object stateObj)
        {
            Thread.Sleep(TimeSpan.FromSeconds(1));
            Console.Write("*** シグナル状態に設定 *** ");
            (stateObj as ManualResetEventSlim).Set();
        }
    }
    #endregion


実行結果は以下のようになります。

  メインスレッド待機中・・・*** シグナル状態に設定 *** 終了
  メインスレッド待機中・・・*** 操作はキャンセルされました。 *** 終了

別スレッドでシグナル状態に設定する前に、キャンセル操作を行っているので
シグナル状態に設定されずにキャンセルされています。


上記のサンプルの

tokenSource.Cancel();

の部分をコメントアウトして実行すると

  メインスレッド待機中・・・*** シグナル状態に設定 *** 終了
  メインスレッド待機中・・・*** シグナル状態に設定 *** 終了

となります。



追記)
後、ちょこっとした違いですが

ManualResetEventSlimクラスは、EventWaitHandleクラスを継承していません。


元となる待機ハンドルを取得する場合は

  • WaitHandle

プロパティから取得します。



================================
過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。