Task.WaitAll抛出OperationCanceledException异常

5

我有一个使用相同CancellationTokenSource的正在运行任务列表。

我希望当前线程等待直到所有任务完成或者任务被取消。

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

即使当前线程处于等待状态,任务仍可能被另一个任务取消。这是正常的行为。

但是,在当前线程处于等待状态且另一个任务取消任务时,WaitAll会抛出一个带有消息“操作已取消”的CancellationTokenSource

我知道它已被取消,我是有意这样做的。我只想让它在任务被取消或完成后继续执行下一段代码,而不抛出异常。

我知道可以使用try & catch来包装此代码,但抛出异常是一项繁重的操作,我不希望在这种正常行为中发生。


这是官方文档中记录的。如果您不想得到异常,为什么要将 CancellationToken 传递给 WaitAll 函数呢? - Ivan Stoev
4
如果至少有一个任务被取消,文档中也记录了会出现AggregateException。不管你想不想要,这是他们的设计,你别无选择,只能使用try/catch - Ivan Stoev
@PeterDuniho,你没有仔细阅读问题,是吗?我没有抛出任何异常来中断任务(或任何异常)。我只是不希望在取消时抛出异常,这就是全部。我的描述已经很详细了。你写道我抛出异常以中断任务,这是不正确的。请更正。我没有写过这个。谢谢。 - Jacob
“我没有抛出任何异常” - 证明它。提供一个良好的 [mcve],显示在您声称的位置发生异常,而不抛出异常。 - Peter Duniho
3
我看不到任何解决办法。正如@Ivan所说,这已经有文档记录了:MSDN。下面ibebbs的解决方案很聪明,但据我所知,它只是隐藏了异常;它并没有防止异常的发生。 - Jason Boyd
显示剩余5条评论
1个回答

8
这个阻塞机制可以重新表述为:
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();
    }
}

希望能对您有所帮助。

谢谢您的回答。我认为这是一种解决方法。也许您有这样的代码在生产环境中运行吗?如果是这样,它是否正常运行? - Jacob
1
@Jacob 按照设计它会抛出异常,但你不想要那样。所以你确实在寻找解决方法。 - Steve
它运行良好,谢谢! - Jacob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接