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


Отмена задачи

Классы System.Threading.Tasks.Task и System.Threading.Tasks.Task<TResult> поддерживают отмену с помощью маркеров отмены. Дополнительные сведения см. в разделе Отмена в управляемых потоках. В классах 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 .

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

См. также