【问题标题】:What happens when multiple parallel threads await the same Task instance which then throws?当多个并行线程等待同一个 Task 实例然后抛出时会发生什么?
【发布时间】:2017-06-26 12:41:45
【问题描述】:

通读answers to this question 促使我思考当等待的任务抛出时会发生什么异常。所有“客户”都可以观察到异常吗?我承认我可能在这里混淆了几件事;这就是我要求澄清的原因。

我将展示一个具体的场景...假设我有一个服务器,其中包含由客户端启动的长期运行的Task 实例的全局集合。在开始一项或多项任务后,客户端可以查询他们的进度并在结果可用时检索结果,以及可能发生的任何错误。

任务本身可能执行非常不同的业务特定的事情 - 通常,没有两个是完全相同的。但是,如果其中一个客户端确实尝试启动与另一个客户端先前启动的相同的任务,则服务器应识别这一点并将第二个客户端“附加”到现有任务,而不是假脱机一个新副本。

现在,每当任何客户端查询它感兴趣的任务的状态时,它都会执行以下操作:

var timeout = Task.Delay(numberOfSecondsUntilClientsPatienceRunsOut);
try
{
    var result = await Task.WhenAny(timeout, task);
    if (result == timeout)
    {
        // SNIP: No result is available yet. Just report the progress so far.
    }

    // SNIP: Report the result.
}
catch (Exception e)
{
    // SNIP: Report the error.
}

简而言之,它给了任务一些合理的时间来先完成它正在做的事情,然后再返回报告正在进行的进度。这意味着有一个潜在的重要时间窗口,多个客户端可能会观察到相同的任务失败。

我的问题是:如果任务恰好在此窗口期间抛出,是否所有客户端都观察到(并处理)了异常?

【问题讨论】:

  • "返回的任务将在任何提供的任务完成后完成。返回的任务将始终以RanToCompletion 状态结束,其Result 设置为要完成的第一个任务。这是即使第一个要完成的任务以 Canceled 或 Faulted 状态结束,也是如此。"如果task 出错,并且已完成,并且多个客户端正在通过此构造等待它,那么所有客户端都将看到出错的任务。如果他们试图检查task(不是result)的结果,他们都会得到一个异常。这不是“先到先得”的交易。
  • 为了进一步简化这一点:如果您连续多次等待Task.Run(() => throw new Exception(DateTime.Now.Ticks.ToString()))(使用异常处理程序),您每次都会收到一个带有相同时间戳的异常(以证明任务没有执行多次)。一个出错的任务可以产生任意数量的异常。
  • 有趣的问题,但很容易测试自己。
  • @JeroenMostert:谢谢。请将此作为答案发布,以便我接受!

标签: c# asynchronous task


【解决方案1】:

Task.WhenAny 不会抛出自己。每the documentation

返回的任务将在任何提供的任务完成时完成 完全的。返回的任务将始终以RanToCompletion 结尾 将其 Result 设置为要完成的第一个任务的状态。这是真实的 即使要完成的第一个任务以Canceled 结束,或者 Faulted 状态。

您将返回timeouttask

如果结果是task,并且您等待(或得到Task.Result),并且task 出现故障,那么它将抛出。有多少调用者这样做,或者他们何时这样做并不重要——试图获得错误任务的结果总是会抛出。简单的代码来证明这一点:

var t = Task.Run(() => throw new Exception(DateTime.Now.Ticks.ToString()));
try {
    await t;
} catch (Exception e) {
    Console.WriteLine(e.Message);
}
await Task.Delay(1000);
try {
    await t;
} catch (Exception e) {
    Console.WriteLine(e.Message);
}

这将打印相同的时间戳两次。该任务只运行一次,并且只有一个结果,每次尝试获取它时都会产生异常。如果您愿意,可以混合不同的线程或并行调用,这不会改变结果。

请注意,在超时的情况下,仍然存在竞争条件的基本可能性:两个不同的任务/线程,都在等待同一个任务,可能会在 await Task.WhenAny(timeout, task) 上得到不同的结果,这取决于他们观察到的任务先完成。换句话说,即使await Task.WhenAny(timeout, task) == timeouttask 仍然可以在.WhenAny() 决定它已经完成和控制最终返回给你之间的任何时候出现故障。这是意料之中的,您的代码应该处理这个问题(在下一轮等待中,.WhenAny() 将立即返回错误任务)。

【讨论】:

  • 致任何反对者:请留下评论,解释为什么答案是错误的,或者如何改进。
猜你喜欢
  • 2022-11-26
  • 1970-01-01
  • 2019-01-05
  • 2021-12-15
  • 2010-12-12
  • 2017-11-18
  • 2022-01-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多