Отмена в управляемых потокахCancellation in Managed Threads

В .NET Framework 4 введена новая универсальная модель совместной отмены асинхронных или долго выполняющихся синхронных операций.Starting with the .NET Framework 4, the .NET Framework uses a unified model for cooperative cancellation of asynchronous or long-running synchronous operations. Эта модель построена на простом объекте, называемом токеном отмены.This model is based on a lightweight object called a cancellation token. Объект, который вызывает одну или несколько отменяемых операций, например, путем создания новых потоков или задач, передает этот токен в каждую операцию.The object that invokes one or more cancelable operations, for example by creating new threads or tasks, passes the token to each operation. Операция, в свою очередь, передает копии этого токена в другие операции.Individual operations can in turn pass copies of the token to other operations. Некоторое время спустя объект, создавший токен, может использовать его для запроса остановки выполнения операции.At some later time, the object that created the token can use it to request that the operations stop what they are doing. Запрос на отмену может создавать только запрашивающий объект, и каждый прослушиватель должен обнаружить этот запрос, чтобы правильно и своевременно отреагировать на него.Only the requesting object can issue the cancellation request, and each listener is responsible for noticing the request and responding to it in an appropriate and timely manner.

Общая схема реализации модели совместной отмены выглядит следующим образом:The general pattern for implementing the cooperative cancellation model is:

  • Создается экземпляр объекта CancellationTokenSource, который управляет уведомлениями об отмене и передает их отдельным токенам отмены.Instantiate a CancellationTokenSource object, which manages and sends cancellation notification to the individual cancellation tokens.

  • В каждую задачу или поток, ожидающий отмены, передается токен, возвращенный свойством CancellationTokenSource.Token.Pass the token returned by the CancellationTokenSource.Token property to each task or thread that listens for cancellation.

  • Каждой задачи или каждому потоку предоставляется механизм реагирования на отмену.Provide a mechanism for each task or thread to respond to cancellation.

  • Вызывается метод CancellationTokenSource.Cancel для предоставления уведомления об отмене.Call the CancellationTokenSource.Cancel method to provide notification of cancellation.

Важно!

Класс CancellationTokenSource реализует интерфейс IDisposable.The CancellationTokenSource class implements the IDisposable interface. По завершении использования источника токена отмены обязательно вызовите метод CancellationTokenSource.Dispose, чтобы освободить все занятые неуправляемые ресурсы.You should be sure to call the CancellationTokenSource.Dispose method when you have finished using the cancellation token source to free any unmanaged resources it holds.

На рисунке ниже показана связь между источником токена и всеми копиями токена.The following illustration shows the relationship between a token source and all the copies of its token.

CancellationTokenSource и маркеры отменыCancellationTokenSource and cancellation tokens

Новая модель отмены упрощает создание приложений и библиотек, поддерживающих отмену. Она также поддерживает перечисленные ниже возможности.The new cancellation model makes it easier to create cancellation-aware applications and libraries, and it supports the following features:

  • Отмена является совместной и не осуществляется принудительно на прослушивателе.Cancellation is cooperative and is not forced on the listener. Прослушиватель сам определяет порядок корректного завершения в ответ на запрос отмены.The listener determines how to gracefully terminate in response to a cancellation request.

  • Запрос осуществляется отдельно от прослушивания.Requesting is distinct from listening. Объект, который вызывает отменяемую операцию, может управлять временем создания запроса отмены (а также самим фактом создания подобного запроса).An object that invokes a cancelable operation can control when (if ever) cancellation is requested.

  • Запрашивающий объект создает запрос на отмену для всех копий токена, используя только один вызов метода.The requesting object issues the cancellation request to all copies of the token by using just one method call.

  • Прослушиватель может одновременно ожидать несколько маркеров, объединив их в один связанный маркер.A listener can listen to multiple tokens simultaneously by joining them into one linked token.

  • Пользовательский код может отслеживать запросы на отмену из кода библиотеки и реагировать на них, а код библиотеки, в свою очередь, может отслеживать запросы на отмену из пользовательского кода и реагировать на них.User code can notice and respond to cancellation requests from library code, and library code can notice and respond to cancellation requests from user code.

  • Для уведомления прослушивателей о запросах на отмену может использоваться опрос, регистрация обратных вызовов или ожидание дескрипторов ожидания.Listeners can be notified of cancellation requests by polling, callback registration, or waiting on wait handles.

Типы отменыCancellation Types

Инфраструктура отмены реализована в виде набора связанных типов, приведенных в таблице ниже.The cancellation framework is implemented as a set of related types, which are listed in the following table.

Имя типаType name ОПИСАНИЕDescription
CancellationTokenSource Объект, который создает токен отмены и запрос на отмену для всех копий этого токена.Object that creates a cancellation token, and also issues the cancellation request for all copies of that token.
CancellationToken Простой тип значения, передаваемый одному или нескольким прослушивателям, обычно в виде параметра метода.Lightweight value type passed to one or more listeners, typically as a method parameter. Прослушиватели отслеживают значение свойства IsCancellationRequested токена посредством опроса, обратного вызова или дескриптора ожидания.Listeners monitor the value of the IsCancellationRequested property of the token by polling, callback, or wait handle.
OperationCanceledException Перегрузки конструктора этого исключения принимают CancellationToken в качестве параметра.Overloads of this exception's constructor accept a CancellationToken as a parameter. Прослушиватели могут также создавать это исключение для проверки источника отмены и уведомления остальных прослушивателей об ответе на запрос отмены.Listeners can optionally throw this exception to verify the source of the cancellation and notify others that it has responded to a cancellation request.

Новая модель отмены интегрирована в несколько типов .NET Framework.The new cancellation model is integrated into the .NET Framework in several types. Наиболее важные из них — System.Threading.Tasks.Parallel, System.Threading.Tasks.Task, System.Threading.Tasks.Task<TResult> и System.Linq.ParallelEnumerable.The most important ones are System.Threading.Tasks.Parallel, System.Threading.Tasks.Task, System.Threading.Tasks.Task<TResult> and System.Linq.ParallelEnumerable. Мы рекомендуем использовать именно эту новую модель отмены в коде всех новых библиотек и приложений.We recommend that you use this new cancellation model for all new library and application code.

Пример кодаCode Example

В примере ниже запрашивающий объект создает объект CancellationTokenSource, а затем передает его свойство Token в отменяемую операцию.In the following example, the requesting object creates a CancellationTokenSource object, and then passes its Token property to the cancelable operation. Операция, получающая запрос, отслеживает значение свойства IsCancellationRequested токена путем опроса.The operation that receives the request monitors the value of the IsCancellationRequested property of the token by polling. Когда свойство принимает значение true, прослушиватель может завершить операцию любым приемлемым способом.When the value becomes true, the listener can terminate in whatever manner is appropriate. В этом примере просто выполняется выход из метода. Во многих случаях этого достаточно.In this example, the method just exits, which is all that is required in many cases.

Примечание

В этом примере метод QueueUserWorkItem используется для демонстрации совместимости новой инфраструктуры отмены с устаревшими интерфейсами API.The example uses the QueueUserWorkItem method to demonstrate that the new cancellation framework is compatible with legacy APIs. Пример, в котором используется новый предпочтительный тип System.Threading.Tasks.Task, см. в разделе Руководство. Отмена задачи и ее дочерних элементов.For an example that uses the new, preferred System.Threading.Tasks.Task type, see How to: Cancel a Task and Its Children.

using System;
using System.Threading;

public class Example
{
   public static void Main()
   {
      // Create the token source.
      CancellationTokenSource cts = new CancellationTokenSource();

      // Pass the token to the cancelable operation.
      ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), cts.Token);
      Thread.Sleep(2500);

      // Request cancellation.
      cts.Cancel();
      Console.WriteLine("Cancellation set in token source...");
      Thread.Sleep(2500);
      // Cancellation should have happened, so call Dispose.
      cts.Dispose();
   }

   // Thread 2: The listener
   static void DoSomeWork(object obj)
   {
      CancellationToken token = (CancellationToken)obj;

      for (int i = 0; i < 100000; i++) {
         if (token.IsCancellationRequested)
         {
            Console.WriteLine("In iteration {0}, cancellation has been requested...",
                              i + 1);
            // Perform cleanup if necessary.
            //...
            // Terminate the operation.
            break;
         }
         // Simulate some work.
         Thread.SpinWait(500000);
      }
   }
}
// The example displays output like the following:
//       Cancellation set in token source...
//       In iteration 1430, cancellation has been requested...
Imports System.Threading

Module Example
   Public Sub Main()
      ' Create the token source.
      Dim cts As New CancellationTokenSource()

      ' Pass the token to the cancelable operation.
      ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf DoSomeWork), cts.Token)
      Thread.Sleep(2500)
        
      ' Request cancellation by setting a flag on the token.
      cts.Cancel()
      Console.WriteLine("Cancellation set in token source...")
      Thread.Sleep(2500)
      ' Cancellation should have happened, so call Dispose.
      cts.Dispose()
   End Sub

   ' Thread 2: The listener
   Sub DoSomeWork(ByVal obj As Object)
      Dim token As CancellationToken = CType(obj, CancellationToken)
      
      For i As Integer = 0 To 1000000
         If token.IsCancellationRequested Then
            Console.WriteLine("In iteration {0}, cancellation has been requested...",
                              i + 1)
            ' Perform cleanup if necessary.
            '...
            ' Terminate the operation.
            Exit For
         End If
      
         ' Simulate some work.
         Thread.SpinWait(500000)
      Next
   End Sub
End Module
' The example displays output like the following:
'       Cancellation set in token source...
'       In iteration 1430, cancellation has been requested...

Отмена операции и отмена объектаOperation Cancellation Versus Object Cancellation

В рамках новой инфраструктуры отмены осуществляется отмена операций, а не объектов.In the new cancellation framework, cancellation refers to operations, not objects. Запрос на отмену означает, что операция должна быть остановлена как можно скорее после выполнения всех необходимых очисток.The cancellation request means that the operation should stop as soon as possible after any required cleanup is performed. Один токен отмены должен относиться к одной отменяемой операции, однако эта операция может быть реализована в программе.One cancellation token should refer to one "cancelable operation," however that operation may be implemented in your program. После того как свойство IsCancellationRequested токена примет значение true, для него невозможно будет восстановить значение false.After the IsCancellationRequested property of the token has been set to true, it cannot be reset to false. Поэтому токены отмены нельзя использовать повторно после отмены.Therefore, cancellation tokens cannot be reused after they have been canceled.

Если вам необходим механизм отмены объектов, его можно построить на основе механизма отмены операций путем вызова метода CancellationToken.Register, как показано в примере ниже.If you require an object cancellation mechanism, you can base it on the operation cancellation mechanism by calling the CancellationToken.Register method, as shown in the following example.

using System;
using System.Threading;

class CancelableObject
{
   public string id;
   
   public CancelableObject(string id)
   {
      this.id = id;
   }
   
   public void Cancel() 
   { 
      Console.WriteLine("Object {0} Cancel callback", id);
      // Perform object cancellation here.
   }
}

public class Example
{
   public static void Main()
   {
      CancellationTokenSource cts = new CancellationTokenSource();
      CancellationToken token = cts.Token;

      // User defined Class with its own method for cancellation
      var obj1 = new CancelableObject("1");
      var obj2 = new CancelableObject("2");
      var obj3 = new CancelableObject("3");

      // Register the object's cancel method with the token's
      // cancellation request.
      token.Register(() => obj1.Cancel());
      token.Register(() => obj2.Cancel());
      token.Register(() => obj3.Cancel());

      // Request cancellation on the token.
      cts.Cancel();
      // Call Dispose when we're done with the CancellationTokenSource.
      cts.Dispose();
   }
}
// The example displays the following output:
//       Object 3 Cancel callback
//       Object 2 Cancel callback
//       Object 1 Cancel callback
Imports System.Threading

Class CancelableObject
   Public id As String
   
   Public Sub New(id As String)
      Me.id = id
   End Sub
   
   Public Sub Cancel() 
      Console.WriteLine("Object {0} Cancel callback", id)
      ' Perform object cancellation here.
   End Sub
End Class

Module Example
   Public Sub Main()
        Dim cts As New CancellationTokenSource()
        Dim token As CancellationToken = cts.Token

        ' User defined Class with its own method for cancellation
        Dim obj1 As New CancelableObject("1")
        Dim obj2 As New CancelableObject("2")
        Dim obj3 As New CancelableObject("3")

        ' Register the object's cancel method with the token's
        ' cancellation request.
        token.Register(Sub() obj1.Cancel())
        token.Register(Sub() obj2.Cancel())
        token.Register(Sub() obj3.Cancel())

        ' Request cancellation on the token.
        cts.Cancel()
        ' Call Dispose when we're done with the CancellationTokenSource.
        cts.Dispose()
   End Sub
End Module
' The example displays output like the following:
'       Object 3 Cancel callback
'       Object 2 Cancel callback
'       Object 1 Cancel callback

Если объект поддерживает несколько параллельных отменяемых операций, в каждую отменяемую операцию следует передавать отдельный токен.If an object supports more than one concurrent cancelable operation, pass a separate token as input to each distinct cancelable operation. Это позволяет отменить одну операцию, не затрагивая при этом остальные.That way, one operation can be cancelled without affecting the others.

Прослушивание запросов на отмену и ответ на нихListening and Responding to Cancellation Requests

Объект, реализующий отменяемую операцию, в пользовательском делегате определяет способ завершения операции в ответ на запрос отмены.In the user delegate, the implementer of a cancelable operation determines how to terminate the operation in response to a cancellation request. Во многих случаях пользовательский делегат может выполнить необходимую очистку, а затем немедленный возврат.In many cases, the user delegate can just perform any required cleanup and then return immediately.

Однако в более сложных случаях может потребоваться, чтобы пользовательский делегат уведомлял код библиотеки об отмене.However, in more complex cases, it might be necessary for the user delegate to notify library code that cancellation has occurred. В таких случаях, чтобы правильно завершить операцию, следует вызвать из делегата метод ThrowIfCancellationRequested, который создает исключение OperationCanceledException.In such cases, the correct way to terminate the operation is for the delegate to call the ThrowIfCancellationRequested, method, which will cause an OperationCanceledException to be thrown. Код библиотеки может перехватить это исключение в потоке пользовательского делегата и проверить токен исключения, чтобы определить, указывает ли исключение на совместную отмену или возникновение другой исключительной ситуации.Library code can catch this exception on the user delegate thread and examine the exception's token to determine whether the exception indicates cooperative cancellation or some other exceptional situation.

Класс Task обрабатывает OperationCanceledException таким образом.The Task class handles OperationCanceledException in this way. Дополнительные сведения см. в разделе Отмена задач.For more information, see Task Cancellation.

Прослушивание с помощью опросовListening by Polling

Для длительных циклических или рекурсивных вычислений можно прослушивать запрос на отмену путем периодического опроса значения свойства CancellationToken.IsCancellationRequested.For long-running computations that loop or recurse, you can listen for a cancellation request by periodically polling the value of the CancellationToken.IsCancellationRequested property. Если его значение равно true, метод должен максимально быстро выполнить очистку и завершение.If its value is true, the method should clean up and terminate as quickly as possible. Оптимальная частота опроса зависит от типа приложения.The optimal frequency of polling depends on the type of application. Разработчик должен определить оптимальную частоту опроса для конкретной программы.It is up to the developer to determine the best polling frequency for any given program. Сам по себе опрос не оказывает значительного влияния на производительность.Polling itself does not significantly impact performance. В примере ниже показан один из возможных способов опроса.The following example shows one possible way to poll.

static void NestedLoops(Rectangle rect, CancellationToken token)
{
   for (int x = 0; x < rect.columns && !token.IsCancellationRequested; x++) {
      for (int y = 0; y < rect.rows; y++) {
         // Simulating work.
         Thread.SpinWait(5000);
         Console.Write("{0},{1} ", x, y);
      }

      // Assume that we know that the inner loop is very fast.
      // Therefore, checking once per row is sufficient.
      if (token.IsCancellationRequested) {
         // Cleanup or undo here if necessary...
         Console.WriteLine("\r\nCancelling after row {0}.", x);
         Console.WriteLine("Press any key to exit.");
         // then...
         break;
         // ...or, if using Task:
         // token.ThrowIfCancellationRequested();
      }
   }
}
Shared Sub NestedLoops(ByVal rect As Rectangle, ByVal token As CancellationToken)
    For x As Integer = 0 To rect.columns
        For y As Integer = 0 To rect.rows
            ' Simulating work.
            Thread.SpinWait(5000)
            Console.Write("0' end block,1' end block ", x, y)
        Next

        ' Assume that we know that the inner loop is very fast.
        ' Therefore, checking once per row is sufficient.
        If token.IsCancellationRequested = True Then
            ' Cleanup or undo here if necessary...
            Console.WriteLine(vbCrLf + "Cancelling after row 0' end block.", x)
            Console.WriteLine("Press any key to exit.")
            ' then...
            Exit For
            ' ...or, if using Task:
            ' token.ThrowIfCancellationRequested()
        End If
    Next
End Sub

Более полный пример см. в подразделе Практическое руководство. Прослушивание запросов на отмену посредством опросов.For a more complete example, see How to: Listen for Cancellation Requests by Polling.

Прослушивание путем регистрации обратного вызоваListening by Registering a Callback

Некоторые операции могут быть заблокированы таким образом, при котором невозможно своевременно проверить значение токена отмены.Some operations can become blocked in such a way that they cannot check the value of the cancellation token in a timely manner. В этих случаях можно зарегистрировать метод обратного вызова, который разблокирует метод при получении запроса на отмену.For these cases, you can register a callback method that unblocks the method when a cancellation request is received.

Метод Register возвращает объект CancellationTokenRegistration, который используется специально в этих целях.The Register method returns a CancellationTokenRegistration object that is used specifically for this purpose. В примере ниже показано, как использовать метод Register для отмены асинхронного веб-запроса.The following example shows how to use the Register method to cancel an asynchronous Web request.

using System;
using System.Net;
using System.Threading;

class Example
{
    static void Main()
    {
        CancellationTokenSource cts = new CancellationTokenSource();

        StartWebRequest(cts.Token);

        // cancellation will cause the web 
        // request to be cancelled
        cts.Cancel();
    }

    static void StartWebRequest(CancellationToken token)
    {
        WebClient wc = new WebClient();
        wc.DownloadStringCompleted += (s, e) => Console.WriteLine("Request completed.");

        // Cancellation on the token will 
        // call CancelAsync on the WebClient.
        token.Register(() =>
        {
            wc.CancelAsync();
            Console.WriteLine("Request cancelled!");
        });

        Console.WriteLine("Starting request.");
        wc.DownloadStringAsync(new Uri("http://www.contoso.com"));
    }
}
Imports System.Net
Imports System.Threading

Class Example
    Private Shared Sub Main()
        Dim cts As New CancellationTokenSource()

        StartWebRequest(cts.Token)

        ' cancellation will cause the web 
        ' request to be cancelled
        cts.Cancel()
    End Sub

    Private Shared Sub StartWebRequest(token As CancellationToken)
        Dim wc As New WebClient()
        wc.DownloadStringCompleted += Function(s, e) Console.WriteLine("Request completed.")

        ' Cancellation on the token will 
        ' call CancelAsync on the WebClient.
        token.Register(Function() 
        wc.CancelAsync()
        Console.WriteLine("Request cancelled!")

End Function)

        Console.WriteLine("Starting request.")
        wc.DownloadStringAsync(New Uri("http://www.contoso.com"))
    End Sub
End Class

Объект CancellationTokenRegistration управляет синхронизацией потока и обеспечивает прекращение выполнения обратного вызова в определенный момент времени.The CancellationTokenRegistration object manages thread synchronization and ensures that the callback will stop executing at a precise point in time.

Чтобы обеспечить отклик системы и предотвратить взаимоблокировки, при регистрации обратных вызовов необходимо следовать приведенным ниже рекомендациям.In order to ensure system responsiveness and to avoid deadlocks, the following guidelines must be followed when registering callbacks:

  • Метод обратного вызова должен быть быстрым, так как он вызывается синхронно и поэтому возврат вызова Cancel будет выполнен после возврата из функции обратного вызова.The callback method should be fast because it is called synchronously and therefore the call to Cancel does not return until the callback returns.

  • Если вы вызываете Dispose во время выполнения обратного вызова и удерживаете блокировку, которую ожидает функция обратного вызова, в программе может произойти взаимоблокировка.If you call Dispose while the callback is running, and you hold a lock that the callback is waiting on, your program can deadlock. После завершения работы метода Dispose можно освобождать любые ресурсы, которые необходимы для обратного вызова.After Dispose returns, you can free any resources required by the callback.

  • Обратные вызовы не должны обрабатывать какие-либо ручные потоки или использовать SynchronizationContext в обратном вызове.Callbacks should not perform any manual thread or SynchronizationContext usage in a callback. Если обратный вызов должен выполняться в определенном потоке, используйте конструктор System.Threading.CancellationTokenRegistration, который позволяет задать активный объект SynchronizationContext.Current в качестве целевого объекта syncContext.If a callback must run on a particular thread, use the System.Threading.CancellationTokenRegistration constructor that enables you to specify that the target syncContext is the active SynchronizationContext.Current. Выполнение ручного потока в обратном вызове может привести к взаимоблокировке.Performing manual threading in a callback can cause deadlock.

Более полный пример см. в подразделе Практическое руководство. Регистрация обратных вызовов для запросов на отмену.For a more complete example, see How to: Register Callbacks for Cancellation Requests.

Прослушивание с помощью дескриптора ожиданияListening by Using a Wait Handle

В случаях, когда отменяемая операция может блокироваться на время ожидания примитива синхронизации, такого как System.Threading.ManualResetEvent или System.Threading.Semaphore, можно с помощью свойства CancellationToken.WaitHandle включить ожидание операцией как этого события, так и запроса на отмену.When a cancelable operation can block while it waits on a synchronization primitive such as a System.Threading.ManualResetEvent or System.Threading.Semaphore, you can use the CancellationToken.WaitHandle property to enable the operation to wait on both the event and the cancellation request. Дескриптору ожидания токена отмены будет отправлен сигнал в ответ на запрос отмены, и метод сможет с помощью возвращаемого значения метода WaitAny определить, был ли этот сигнал отправлен токеном отмены.The wait handle of the cancellation token will become signaled in response to a cancellation request, and the method can use the return value of the WaitAny method to determine whether it was the cancellation token that signaled. Затем операция может выполнить выход или создать исключение OperationCanceledException в зависимости от ситуации.The operation can then just exit, or throw a OperationCanceledException, as appropriate.

// Wait on the event if it is not signaled.
int eventThatSignaledIndex =
       WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },
                          new TimeSpan(0, 0, 20));
' Wait on the event if it is not signaled.
Dim waitHandles() As WaitHandle = { mre, token.WaitHandle }
Dim eventThatSignaledIndex =
    WaitHandle.WaitAny(waitHandles, _
                       New TimeSpan(0, 0, 20))

В новом коде, предназначенном для .NET Framework 4, классы System.Threading.ManualResetEventSlim и System.Threading.SemaphoreSlim обеспечивают поддержку новой инфраструктуры отмены в методах Wait.In new code that targets the .NET Framework 4, System.Threading.ManualResetEventSlim and System.Threading.SemaphoreSlim both support the new cancellation framework in their Wait methods. Вы можете передать этому методу CancellationToken, и тогда это событие активируется и создает исключение OperationCanceledException, когда поступает запрос на отмену.You can pass the CancellationToken to the method, and when the cancellation is requested, the event wakes up and throws an OperationCanceledException.

try
{
    // mres is a ManualResetEventSlim
    mres.Wait(token);
}
catch (OperationCanceledException)
{
    // Throw immediately to be responsive. The
    // alternative is to do one more item of work,
    // and throw on next iteration, because
    // IsCancellationRequested will be true.
    Console.WriteLine("The wait operation was canceled.");
    throw;
}

Console.Write("Working...");
// Simulating work.
Thread.SpinWait(500000);
Try
   ' mres is a ManualResetEventSlim
    mres.Wait(token)
Catch e As OperationCanceledException
    ' Throw immediately to be responsive. The
    ' alternative is to do one more item of work,
    ' and throw on next iteration, because
    ' IsCancellationRequested will be true.
    Console.WriteLine("Canceled while waiting.")
    Throw
End Try

 ' Simulating work.
Console.Write("Working...")
Thread.SpinWait(500000)

Более полный пример см. в подразделе Практическое руководство. Прослушивание запросов на отмену, содержащих дескрипторы ожидания.For a more complete example, see How to: Listen for Cancellation Requests That Have Wait Handles.

Одновременное прослушивание нескольких токеновListening to Multiple Tokens Simultaneously

В некоторых случаях прослушивателю может требоваться одновременного прослушивать несколько токенов отмены.In some cases, a listener may have to listen to multiple cancellation tokens simultaneously. Например, отменяемая операция может в дополнение к токену отмены, переданному извне в качестве аргумента в параметр метода, отслеживать также внутренний токен отмены.For example, a cancelable operation may have to monitor an internal cancellation token in addition to a token passed in externally as an argument to a method parameter. Для этого создайте источник связанных токенов, который может объединять два или более токенов в один, как показано в примере ниже.To accomplish this, create a linked token source that can join two or more tokens into one token, as shown in the following example.

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();
           }
       }
   }
}
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

Следует отметить, что после выполнения источником связанных токенов всех возложенных на него функций необходимо вызвать для него метод Dispose.Notice that you must call Dispose on the linked token source when you are done with it. Более полный пример см. в подразделе Практическое руководство. Прослушивание нескольких запросов на отмену.For a more complete example, see How to: Listen for Multiple Cancellation Requests.

Совместная работа кода библиотеки и пользовательского кодаCooperation Between Library Code and User Code

Унифицированная инфраструктура отмены позволяет коду библиотеки отменять пользовательский код, а пользовательскому коду — отменять код библиотеки по принципу совместной работы.The unified cancellation framework makes it possible for library code to cancel user code, and for user code to cancel library code in a cooperative manner. Успешная совместная работа зависит от соблюдения каждой стороной перечисленных ниже рекомендаций.Smooth cooperation depends on each side following these guidelines:

  • Если код библиотеки предоставляет отменяемые операции, он также должен предоставить общие методы, принимающие внешний токен отмены, чтобы пользовательский код мог запрашивать отмену.If library code provides cancelable operations, it should also provide public methods that accept an external cancellation token so that user code can request cancellation.

  • Если код библиотеки вызывает пользовательский код, он должен уметь обрабатывать исключение OperationCanceledException(externalToken) как совместную отмену, а не только как исключение сбоя.If library code calls into user code, the library code should interpret an OperationCanceledException(externalToken) as cooperative cancellation, and not necessarily as a failure exception.

  • Пользовательские делегаты должны пытаться своевременно отвечать на запросы отмены от кода библиотеки.User-delegates should attempt to respond to cancellation requests from library code in a timely manner.

System.Threading.Tasks.Task и System.Linq.ParallelEnumerable — примеры классов, соответствующих этим рекомендациям.System.Threading.Tasks.Task and System.Linq.ParallelEnumerable are examples of classes that follow these guidelines. Дополнительные сведения см. в разделах Отмена задач и Практическое руководство. Отмена запроса PLINQ.For more information, see Task Cancellation and How to: Cancel a PLINQ Query.

См. такжеSee also