【问题标题】:Task.WaitAll throws OperationCanceledException [closed]Task.WaitAll 抛出 OperationCanceledException [关闭]
【发布时间】:2017-03-28 12:03:00
【问题描述】:

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

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

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

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

但是,当当前线程处于等待状态并且另一个任务取消任务时,WaitAll 会抛出 CancellationTokenSource 并显示一条消息:“操作已取消。”。

我知道它被取消了,我是故意这样做的。我只是希望它在任务被取消或完成后继续下一个代码,而不抛出异常。

我知道我可以用 try & catch 包装这段代码,但抛出异常是繁重的操作,我不希望它发生在这样的正常行为上。

【问题讨论】:

  • 它是documented。如果您不想获得异常,为什么要将CancellationToken 传递给WaitAll
  • 我传递它是为了取消等待,但不幸的是,即使我没有传递它,它也会抛出这个异常。 @IvanStoev
  • 据记载,如果至少一项任务被取消,您会收到AggregateException。要不要,这就是他们设计的方式,你别无选择,只能使用try/catch
  • “我不希望它发生在这样的正常行为上”——然后​​不要使用异常来中断任务。如果没有一个好的minimal reproducible example 清楚地说明您的问题,就不可能提供一个好的答案。但是当你希望你的任务被中断时,语言中没有任何东西会强迫你抛出异常。您可以毫无例外地从任务中返回,并提供一些其他机制(如果需要)来区分正常完成和中断。
  • 我看不出有什么办法。正如@Ivan 所说,它被记录在案:MSDN。 ibebbs 下面的解决方案很聪明,但据我所知,它只是隐藏了异常;但它并不能阻止异常的发生。

标签: c# multithreading task-parallel-library multitasking


【解决方案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();
    }
}

希望对你有帮助。

【讨论】:

  • 感谢您的回答。我认为这是一种解决方法。也许你有这样的代码在生产中运行?如果是这样,它运行正常吗?
  • @Jacob 按照设计它会引发异常,但您不希望这样。因此,您确实在寻找解决方法。
  • 效果很好,谢谢!
猜你喜欢
  • 2020-05-30
  • 1970-01-01
  • 2012-09-15
  • 1970-01-01
  • 2017-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多