Task.WaitAll создает исключение OperationCanceledException

У меня есть список запущенных задач с таким же CancellationTokenSource,

Я хочу, чтобы текущий поток ждал, пока все задачи не будут выполнены или пока задачи не будут отменены.

Task.WaitAll(tasks.ToArray(), searchCencellationTokenSource.Token);
System.Console.WriteLine("Done !");

Задачи могут быть отменены другой задачей, даже если текущий поток находится в состоянии ожидания. Это нормальное поведение.

Однако, пока текущий поток находится в состоянии ожидания и другая задача отменяет задачи, WaitAll выбрасывает CancellationTokenSource с сообщением: "Операция была отменена".

Я знаю, что это было отменено, я сделал это намеренно. Я просто хочу перейти к следующему коду после того, как задачи были отменены или завершены, без исключения.

Я знаю, что могу обернуть этот код с помощью try & catch, но создание исключения - тяжелая операция, и я не хочу, чтобы это происходило при нормальном поведении, подобном этому.

1 ответ

Решение

Этот механизм блокировки можно перефразировать как:

Task.WhenAll(taskA, taskB, taskC).Wait()

Это дает вам задание, которое мы можем ожидать, но также можем отменить отмену. Таким образом, чтобы игнорировать исключение отмены, вы можете сделать следующее:

Task.WhenAll(taskA, taskB, taskC).ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled).Wait();

Который не бросит OperationCancelledException,

Затем его можно включить в метод расширения следующим образом:

public static class TaskExtensions
{
    public static Task IgnoreCancellation(this Task task)
    {
        return task.ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled);
    }
}

Что позволит вам написать следующее, опять же, не встречая OperationCancelledException:

Task.WhenAll(taskA, taskB, taskC).IgnoreCancellation().Wait();

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

public class IgnoreTaskCancellation
{
    [Fact]
    public void ShouldThrowAnAggregateException()
    {
        CancellationTokenSource cts = new CancellationTokenSource(10);

        Task taskA = Task.Delay(20, cts.Token);
        Task taskB = Task.Delay(20, cts.Token);
        Task taskC = Task.Delay(20, cts.Token);

        Assert.Throws<AggregateException>(() => Task.WhenAll(taskA, taskB, taskC).Wait());
    }

    [Fact]
    public void ShouldNotThrowAnException()
    {
        CancellationTokenSource cts = new CancellationTokenSource(10);

        Task taskA = Task.Delay(20, cts.Token);
        Task taskB = Task.Delay(20, cts.Token);
        Task taskC = Task.Delay(20, cts.Token);

        Task.WhenAll(taskA, taskB, taskC).ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled).Wait();
    }

    [Fact]
    public void ShouldNotThrowAnExceptionUsingIgnore()
    {
        CancellationTokenSource cts = new CancellationTokenSource(10);

        Task taskA = Task.Delay(20, cts.Token);
        Task taskB = Task.Delay(20, cts.Token);
        Task taskC = Task.Delay(20, cts.Token);

        Task.WhenAll(taskA, taskB, taskC).IgnoreCancellation().Wait();
    }
}

Надеюсь, поможет.

Другие вопросы по тегам