Поделиться через


Отмена задач

System.Threading.Tasks.Task<TResult> Классы System.Threading.Tasks.Task поддерживают отмену с помощью маркеров отмены. См. дополнительные сведения об отмене в управляемых потоках. В классах задач отмена включает взаимодействие между пользовательским делегатом, который представляет операцию отмены, и кодом, который запросил отмену. Успешная отмена включает запрашивающий код, который вызывает метод CancellationTokenSource.Cancel, и пользовательский делегат, который своевременно завершает операцию. Операцию можно завершить одним из следующих способов.

  • Возвращаясь из делегата. Во многих сценариях этот вариант достаточно. Однако экземпляр задачи, отмененный таким образом, переходит в TaskStatus.RanToCompletion состояние, а не TaskStatus.Canceled в состояние.

  • Путем создания и передачи маркера OperationCanceledException , по которому была запрошена отмена. Предпочтительный способ выполнения — использовать ThrowIfCancellationRequested метод. Задача, отмененная таким образом, переходит в состояние "Отменено", которое вызывающий код может использовать для проверки ответа задачи на запрос на отмену.

В следующем примере показан базовый шаблон отмены задачи, который вызывает исключение:

Примечание.

Маркер передается делегату пользователя и экземпляру задачи.

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

class Program
{
    static async Task Main()
    {
        var tokenSource2 = new CancellationTokenSource();
        CancellationToken ct = tokenSource2.Token;

        var task = Task.Run(() =>
        {
            // Were we already canceled?
            ct.ThrowIfCancellationRequested();

            bool moreToDo = true;
            while (moreToDo)
            {
                // Poll on this property if you have to do
                // other cleanup before throwing.
                if (ct.IsCancellationRequested)
                {
                    // Clean up here, then...
                    ct.ThrowIfCancellationRequested();
                }
            }
        }, tokenSource2.Token); // Pass same token to Task.Run.

        tokenSource2.Cancel();

        // Just continue on this thread, or await with try-catch:
        try
        {
            await task;
        }
        catch (OperationCanceledException e)
        {
            Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {e.Message}");
        }
        finally
        {
            tokenSource2.Dispose();
        }

        Console.ReadKey();
    }
}
Imports System.Threading
Imports System.Threading.Tasks

Module Test
    Sub Main()
        Dim tokenSource2 As New CancellationTokenSource()
        Dim ct As CancellationToken = tokenSource2.Token

        Dim t2 = Task.Factory.StartNew(Sub()
                                           ' Were we already canceled?
                                           ct.ThrowIfCancellationRequested()

                                           Dim moreToDo As Boolean = True
                                           While moreToDo = True
                                               ' Poll on this property if you have to do
                                               ' other cleanup before throwing.
                                               If ct.IsCancellationRequested Then

                                                   ' Clean up here, then...
                                                   ct.ThrowIfCancellationRequested()
                                               End If

                                           End While
                                       End Sub _
        , tokenSource2.Token) ' Pass same token to StartNew.

        ' Cancel the task.
        tokenSource2.Cancel()

        ' Just continue on this thread, or Wait/WaitAll with try-catch:
        Try
            t2.Wait()

        Catch e As AggregateException

            For Each item In e.InnerExceptions
                Console.WriteLine(e.Message & " " & item.Message)
            Next
        Finally
            tokenSource2.Dispose()
        End Try

        Console.ReadKey()
    End Sub
End Module

Полный пример см. в разделе "Практическое руководство. Отмена задачи и его дочерних элементов".

При обнаружении экземпляра задачи, вызываемого OperationCanceledException пользовательским кодом, он сравнивает маркер исключения с соответствующим маркером (тот, который был передан в API, создавший задачу). Если маркеры одинаковы, а свойство маркера IsCancellationRequested возвращается true, задача интерпретирует это как подтверждение отмены и переход в состояние "Отмена". Если вы не используете Wait или WaitAll метод для ожидания задачи, то задача просто задает его состояние Canceled.

Если вы ждете задачи, которая переходит в состояние "Отмена", System.Threading.Tasks.TaskCanceledException создается исключение (упаковано в AggregateException исключение). Это исключение указывает на успешную отмену вместо неисправной ситуации. Следовательно, свойство Exception задачи возвращает значение null.

Если свойство маркера IsCancellationRequested возвращается false или если маркер исключения не соответствует маркеру задачи, OperationCanceledException он обрабатывается как обычное исключение, что приводит к переходу задачи в состояние сбоя. Наличие других исключений также приведет к переходу задачи в состояние сбоя. Состояние завершения задачи можно получить в свойстве Status .

Возможно, задача может продолжать обрабатывать некоторые элементы после запроса отмены.

См. также