いろいろ備忘録日記

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

.NET クラスライブラリ探訪-043 (System.Threading.CountdownEvent(1))(カウントダウンラッチ, CancelaltionToken, .NET 4.0)


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


CountdownEventクラスは、ManualResetEventSlimクラスなどと同じ待機を行う為のオブジェクトです。
ManualResetEventSlimクラスは、一回Setメソッドを呼ぶとシグナル状態になりますが
CountdownEventは、文字通り一定回数のカウントを持ち、それが全てデクリメントされればシグナル状態となります。
Javaやってる方は、CountDownLatchクラスと大体同じと思って頂ければオッケイです。


結構使い勝手が良いので、私はよく利用する方です。


ManualResetEventSlimクラスと同じく、CountdownEventクラスもキャンセルトークンを
受け付けることが可能です。


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


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


複数回に分けて、このクラスの使い方を取り上げていきます。
まずは、一番シンプルなタイプから。


初期カウント数を1に設定し、それを別の処理がデクリメントします。
メインスレッド側は、カウントが0になるまで待機します。
丁度、ManualResetEventSlimを利用した状態と同じです。


本来、このクラスはThreadPoolなどを利用しているマルチスレッドプログラミングの際に
利用することが多いのですが、今回は面倒なのでTaskを利用しています。
本当は、Taskを利用している場合は、Taskで待機などを行った方が楽です。w


以下、サンプルです。

    #region CountDownEventSamples-01
    /// <summary>
    /// CountdownEventクラスについてのサンプルです。(1)
    /// </summary>
    /// <remarks>
    /// CountdownEventクラスは、.NET 4.0から追加されたクラスです。
    /// JavaのCountDownLatchクラスと同じ機能を持っています。
    /// </remarks>
    public class CountdownEventSamples01 : IExecutable
    {
        public void Execute()
        {
            //
            // 初期カウントが1のCountdownEventオブジェクトを作成.
            //
            // この場合、どこかの処理にてカウントを一つ減らす必要がある。
            // カウントが残っている状態でWaitをしていると、いつまでたってもWaitを
            // 抜けることが出来ない。
            //
            using (CountdownEvent cde = new CountdownEvent(1))
            {
                // 初期の状態を表示.
                Console.WriteLine("InitialCount={0}", cde.InitialCount);
                Console.WriteLine("CurrentCount={0}", cde.CurrentCount);
                Console.WriteLine("IsSet={0}", cde.IsSet);
                
                Task t = Task.Factory.StartNew(() => 
                {
                    Thread.Sleep(TimeSpan.FromSeconds(1));
                    
                    //
                    // カウントをデクリメント.
                    //
                    // Signalメソッドを引数なしで呼ぶと、1つカウントを減らすことが出来る。
                    // (指定した数分、カウントをデクリメントするオーバーロードも存在する。)
                    //
                    // CountdownEvent.CurrentCountが0の状態で、さらにSignalメソッドを呼び出すと
                    // InvalidOperationException (イベントのカウントを 0 より小さい値にデクリメントしようとしました。)が
                    // 発生する。
                    //
                    cde.Signal();
                    cde.Signal(); // このタイミングで例外が発生する.
                });
                
                try
                {
                    t.Wait();
                }
                catch (AggregateException aggEx)
                {
                    foreach (Exception innerEx in aggEx.Flatten().InnerExceptions)
                    {
                        Console.WriteLine("ERROR={0}", innerEx.Message);
                    }
                }

                //
                // カウントが0になるまで待機.
                //
                cde.Wait();
                
                // 現在の状態を表示.
                Console.WriteLine("InitialCount={0}", cde.InitialCount);
                Console.WriteLine("CurrentCount={0}", cde.CurrentCount);
                Console.WriteLine("IsSet={0}", cde.IsSet);
            }
        }
    }
    #endregion


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

  InitialCount=1
  CurrentCount=1
  IsSet=False
  ERROR=イベントのカウントを 0 より小さい値にデクリメントしようとしました。
  InitialCount=1
  CurrentCount=0
  IsSet=True

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

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