いろいろ備忘録日記

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

.NET クラスライブラリ探訪-050 (System.Threading.SemaphoreSlim(2))(セマフォ, 軽量版, キャンセル処理, CancellationToken, .NET 4.0)


今回も、SemaphoreSlimクラスについて。
SemaphoreSlimクラスは、.NET 4.0からSystem.Threading名前空間に追加されたクラスです。
従来から存在していたSemaphoreクラスの計量版という位置づけになります。


SemaphoreSlimクラスのWaitメソッドには、キャンセルトークン(CancellationToken)を
受け付けるオーバーロードが存在します。
利用方法は、以前記述したCountdownEventやBarrierなどと同じで
キャンセルが発行されるとOperationCanceledExceptionが発生します。


[参考記事]

以下、サンプルです。

    #region SemaphoreSlimSamples-02
    /// <summary>
    /// SemaphoreSlimクラスについてのサンプルです。
    /// </summary>
    /// <remarks>
    /// SemaphoreSlimクラスは、.NET 4.0から追加されたクラスです。
    /// 従来から存在していたSemaphoreクラスの軽量版となります。
    /// </remarks>
    public class SemaphoreSlimSamples02 : IExecutable
    {
        public void Execute()
        {
            //
            // SemaphoreSlimのWaitメソッドにはキャンセルトークンを
            // 受け付けるオーバーロードが存在する。
            //
            // CountdownEventやBarrierの場合と同じく、Waitメソッドに
            // キャンセルトークンを指定した場合、別の場所にてキャンセルが
            // 行われると、OperationCanceledExceptionが発生する。
            //
            const int timeout = 2000;
            
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            CancellationToken       token       = tokenSource.Token;
            
            using (SemaphoreSlim semaphore = new SemaphoreSlim(2))
            {
                //
                // あらかじめ、セマフォの上限までWaitしておき
                // 後のスレッドが入れないようにしておく.
                //
                semaphore.Wait();
                semaphore.Wait();
                
                //
                // 3つのタスクを作成する.
                //    1つ目のタスク:キャンセルトークンを指定して無制限待機.
                //    2つ目のタスク:キャンセルトークンとタイムアウト値を指定して待機.
                //    3つ目のタスク:特定時間待機した後、キャンセル処理を行う.
                //
                Parallel.Invoke
                    (
                        () => WaitProc1(semaphore, token),
                        () => WaitProc2(semaphore, timeout, token),
                        () => DoCancel(timeout, tokenSource)
                    );
                
                semaphore.Release();
                semaphore.Release();
                Console.WriteLine("CurrentCount={0}", semaphore.CurrentCount);
            }
        }
        
        // キャンセルトークンを指定して無制限待機.
        void WaitProc1(SemaphoreSlim semaphore, CancellationToken token)
        {
            try
            {
                Console.WriteLine("WaitProc1=待機開始");
                semaphore.Wait(token);
            }
            catch (OperationCanceledException cancelEx)
            {
                Console.WriteLine("WaitProc1={0}", cancelEx.Message);
            }
            finally
            {
                Console.WriteLine("WaitProc1_CurrentCount={0}", semaphore.CurrentCount);
            }
        }
        
        // キャンセルトークンとタイムアウト値を指定して待機.
        void WaitProc2(SemaphoreSlim semaphore, int timeout, CancellationToken token)
        {
            try
            {
                bool isSuccess = semaphore.Wait(timeout, token);
                if (!isSuccess)
                {
                    Console.WriteLine("WaitProc2={0}\t★★タイムアウト★★", isSuccess);
                }
            }
            catch (OperationCanceledException cancelEx)
            {
                Console.WriteLine("WaitProc2={0}", cancelEx.Message);
            }
            finally
            {
                Console.WriteLine("WaitProc2_CurrentCount={0}", semaphore.CurrentCount);
            }
        }
        
        // 特定時間待機した後、キャンセル処理を行う.
        void DoCancel(int timeout, CancellationTokenSource tokenSource)
        {
            Console.WriteLine("待機開始:{0}msec", timeout + 1000);
            Thread.Sleep(timeout + 1000);
            
            Console.WriteLine("待機終了");
            Console.WriteLine("★★キャンセル発行★★");
            tokenSource.Cancel();
        }
    }
    #endregion


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

  WaitProc1=待機開始
  待機開始:3000msec
  WaitProc2=False	★★タイムアウト★★
  WaitProc2_CurrentCount=0
  待機終了
  ★★キャンセル発行★★
  WaitProc1=操作はキャンセルされました。
  WaitProc1_CurrentCount=0
  CurrentCount=2


一つ目のタスクは、キャンセルトークンのみを指定しているので無制限に待機しています。
二つ目のタスクは、キャンセルトークンとタイムアウトを両方指定しています。
なので、先にタイムアウトを迎えて処理が終了しています。
三つ目のタスクが、所定時間を経過後にキャンセルを発行すると、一つ目のWaitはキャンセルされ
OperationCanceledExceptionが発生しています。



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

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