方法: 複数のキャンセル要求を待機するHow to: Listen for Multiple Cancellation Requests

この例では、2 つのキャンセル トークンを同時にリッスンして、いずれかのトークンからキャンセルが要求された場合に操作を取り消す方法を示します。This example shows how to listen to two cancellation tokens simultaneously so that you can cancel an operation if either token requests it.

注意

[マイ コードのみ] が有効になっている場合、Visual Studio では、例外をスローする行で処理が中断され、"ユーザー コードで処理されない例外" に関するエラー メッセージが表示されることがあります。When "Just My Code" is enabled, Visual Studio in some cases will break on the line that throws the exception and display an error message that says "exception not handled by user code." このエラーは問題にはなりません。This error is benign. F5 キーを押して、処理が中断された箇所から続行し、以下の例に示す例外処理動作を確認できます。You can press F5 to continue from it, and see the exception-handling behavior that is demonstrated in the examples below. Visual Studio による処理が最初のエラーで中断しないようにするには、[ツール] メニューの [オプション]、[デバッグ] 、[全般] の順にクリックし、[マイ コードのみ] チェック ボックスをオフにします。To prevent Visual Studio from breaking on the first error, just uncheck the "Just My Code" checkbox under Tools, Options, Debugging, General.

Example

次の例では、CreateLinkedTokenSource メソッドを使用して 2 つのトークンを 1 つのトークンに結合します。In the following example, the CreateLinkedTokenSource method is used to join two tokens into one token. これで、1 つのキャンセル トークンのみを引数として受け取るメソッドにトークンを渡すことができます。This enables the token to be passed to methods that take just one cancellation token as an argument. この例では、クラスの外部から渡されたトークンと、クラス内部で生成されたトークンの両方をメソッドで観察する必要がある一般的なシナリオを示します。The example demonstrates a common scenario in which a method must observe both a token passed in from outside the class, and a token generated inside the class.

using System;
using System.Threading;
using System.Threading.Tasks;

class LinkedTokenSourceDemo
{
  static void Main()
  {
      WorkerWithTimer worker = new WorkerWithTimer();
      CancellationTokenSource cts = new CancellationTokenSource();

      // Task for UI thread, so we can call Task.Wait wait on the main thread.
      Task.Run(() =>
      {
          Console.WriteLine("Press 'c' to cancel within 3 seconds after work begins.");
          Console.WriteLine("Or let the task time out by doing nothing.");
          if (Console.ReadKey(true).KeyChar == 'c')
              cts.Cancel();
      });

      // Let the user read the UI message.
      Thread.Sleep(1000);

      // Start the worker task.
      Task task = Task.Run(() => worker.DoWork(cts.Token), cts.Token);

      try {
          task.Wait(cts.Token);
      }
      catch (OperationCanceledException e) {
          if (e.CancellationToken == cts.Token)
              Console.WriteLine("Canceled from UI thread throwing OCE.");
      }
      catch (AggregateException ae) {
          Console.WriteLine("AggregateException caught: " + ae.InnerException);
          foreach (var inner in ae.InnerExceptions) {
              Console.WriteLine(inner.Message + inner.Source);
          }
      }

      Console.WriteLine("Press any key to exit.");
      Console.ReadKey();
      cts.Dispose();
  }
}

class WorkerWithTimer
{
  CancellationTokenSource internalTokenSource = new CancellationTokenSource();
  CancellationToken internalToken;
  CancellationToken externalToken;
  Timer timer;

  public WorkerWithTimer()
  {
      internalTokenSource = new CancellationTokenSource();
      internalToken = internalTokenSource.Token;

      // A toy cancellation trigger that times out after 3 seconds
      // if the user does not press 'c'.
      timer = new Timer(new TimerCallback(CancelAfterTimeout), null, 3000, 3000);
  }

   public void DoWork(CancellationToken externalToken)
   {
      // Create a new token that combines the internal and external tokens.
      this.internalToken = internalTokenSource.Token;
      this.externalToken = externalToken;

      using (CancellationTokenSource linkedCts =
              CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken))
      {
          try {
              DoWorkInternal(linkedCts.Token);
          }
          catch (OperationCanceledException) {
              if (internalToken.IsCancellationRequested) {
                  Console.WriteLine("Operation timed out.");
              }
              else if (externalToken.IsCancellationRequested) {
                  Console.WriteLine("Cancelling per user request.");
                  externalToken.ThrowIfCancellationRequested();
              }
          }
      }
   }

   private void DoWorkInternal(CancellationToken token)
   {
      for (int i = 0; i < 1000; i++)
      {
          if (token.IsCancellationRequested)
          {
              // We need to dispose the timer if cancellation
              // was requested by the external token.
              timer.Dispose();

              // Throw the exception.
              token.ThrowIfCancellationRequested();
          }

           // Simulating work.
          Thread.SpinWait(7500000);
          Console.Write("working... ");
      }
   }

   public void CancelAfterTimeout(object state)
   {
      Console.WriteLine("\r\nTimer fired.");
      internalTokenSource.Cancel();
      timer.Dispose();
   }
}
Imports System.Threading
Imports System.Threading.Tasks

Class LinkedTokenSourceDemo
   Shared Sub Main()
      Dim worker As New WorkerWithTimer()
      Dim cts As New CancellationTokenSource()

      ' Task for UI thread, so we can call Task.Wait wait on the main thread.
      Task.Run(Sub()
                  Console.WriteLine("Press 'c' to cancel within 3 seconds after work begins.")
                  Console.WriteLine("Or let the task time out by doing nothing.")
                  If Console.ReadKey(True).KeyChar = "c"c Then
                     cts.Cancel()
                  End If
               End Sub)
      ' Let the user read the UI message.
      Thread.Sleep(1000)

      ' Start the worker task.
      Dim t As Task = Task.Run(Sub() worker.DoWork(cts.Token), cts.Token)
      Try
         t.Wait()
      Catch ae As AggregateException
         For Each inner In ae.InnerExceptions
            Console.WriteLine(inner.Message)
         Next
      End Try

      Console.WriteLine("Press any key to exit.")
      Console.ReadKey()
      cts.Dispose()
    End Sub
End Class

Class WorkerWithTimer
   Dim internalTokenSource As CancellationTokenSource
   Dim token As CancellationToken
   Dim timer As Timer

   Public Sub New()
      internalTokenSource = New CancellationTokenSource()
      token = internalTokenSource.Token

      ' A toy cancellation trigger that times out after 3 seconds
      ' if the user does not press 'c'.
      timer = New Timer(New TimerCallback(AddressOf CancelAfterTimeout), Nothing, 3000, 3000)
   End Sub

   Public Sub DoWork(ByVal externalToken As CancellationToken)
      ' Create a new token that combines the internal and external tokens.
      Dim internalToken As CancellationToken = internalTokenSource.Token
      Dim linkedCts As CancellationTokenSource =
      CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken)
      Using (linkedCts)
         Try
            DoWorkInternal(linkedCts.Token)
         Catch e As OperationCanceledException
            If e.CancellationToken = internalToken Then
               Console.WriteLine("Operation timed out.")
            ElseIf e.CancellationToken = externalToken Then
               Console.WriteLine("Canceled by external token.")
               externalToken.ThrowIfCancellationRequested()
            End If
         End Try
      End Using
   End Sub

   Private Sub DoWorkInternal(ByVal token As CancellationToken)
      For i As Integer = 0 To 1000
         If token.IsCancellationRequested Then
            ' We need to dispose the timer if cancellation
            ' was requested by the external token.
           timer.Dispose()

            ' Output for demonstration purposes.
            Console.WriteLine(vbCrLf + "Cancelling per request.")

            ' Throw the exception.
            token.ThrowIfCancellationRequested()
         End If

         ' Simulating work.
         Thread.SpinWait(7500000)
         Console.Write("working... ")
      Next
   End Sub

   Public Sub CancelAfterTimeout(ByVal state As Object)
      Console.WriteLine(vbCrLf + "Timer fired.")
      internalTokenSource.Cancel()
      timer.Dispose()
   End Sub
End Class

リンクされたトークンから OperationCanceledException がスローされる場合、例外に渡されるトークンは先行トークンではなくリンクされたトークンです。When the linked token throws an OperationCanceledException, the token that is passed to the exception is the linked token, not either of the predecessor tokens. 取り消されたトークンを特定するには、先行トークンの状態を直接確認します。To determine which of the tokens was canceled, check the status of the predecessor tokens directly.

この例で AggregateException がスローされることはまずありませんが、実際のシナリオでは、タスクのデリゲートからスローされた OperationCanceledException 以外の例外はすべて AggregateException にラップされるので、ここでキャッチされます。In this example, AggregateException should never be thrown, but it is caught here because in real-world scenarios any other exceptions besides OperationCanceledException that are thrown from the task delegate are wrapped in a AggregateException.

関連項目See also