いろいろ備忘録日記

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

.NET クラスライブラリ探訪-045 (System.Threading.CountdownEvent(3))(キャンセルトークンの利用, CancellationToken, Wait, .NET 4.0)


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


今回は、キャンセルトークン (CancellationToken)を利用した待機のキャンセルについてです。
やり方は、ManualResetEventSlimクラスの場合は同じです。


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


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


以下、サンプルです。

    #region CountdownEventSamples-03
    /// <summary>
    /// CountdownEventクラスについてのサンプルです。(3)
    /// </summary>
    /// <remarks>
    /// CountdownEventクラスは、.NET 4.0から追加されたクラスです。
    /// JavaのCountDownLatchクラスと同じ機能を持っています。
    /// </remarks>
    public class CountdownEventSamples03 : IExecutable
    {
        public void Execute()
        {
            //
            // CountdownEventには、CancellationTokenを受け付けるWaitメソッドが存在する.
            // 使い方は、ManualResetEventSlimクラスの場合と同じ。
            // 
            // 参考リソース:
            //     .NET クラスライブラリ探訪-042 (System.Threading.ManualResetEventSlim)
            //     http://d.hatena.ne.jp/gsf_zero1/20110323/p1
            //
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            CancellationToken       token       = tokenSource.Token;
            
            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(2));
                    
                    token.ThrowIfCancellationRequested();
                    cde.Signal();
                }, token);
                
                //
                // 処理をキャンセル.
                //
                tokenSource.Cancel();
                
                try
                {
                    cde.Wait(token);
                }
                catch (OperationCanceledException cancelEx)
                {
                    if (token == cancelEx.CancellationToken)
                    {
                        Console.WriteLine("***CountdownEvent.Wait()がキャンセルされました***");
                    }
                }
                
                try
                {
                    t.Wait();
                }
                catch (AggregateException aggEx)
                {
                    aggEx.Handle(ex => 
                    {
                        if (ex is OperationCanceledException)
                        {
                            OperationCanceledException cancelEx = ex as OperationCanceledException;
                            
                            if (token == cancelEx.CancellationToken)
                            {
                                Console.WriteLine("***タスクがキャンセルされました***");
                                return true;
                            }
                        }
                        
                        return false;
                    });
                }
                
                // 現在の状態を表示.
                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
  ***CountdownEvent.Wait()がキャンセルされました***
  ***タスクがキャンセルされました***
  InitialCount=1
  CurrentCount=1
  IsSet=False


待機をキャンセルしたので、CurrentCountとIsSetの値が変更されていないことが分かります。


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

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