当多个并行线程等待然后抛出的同一个Task实例时会发生什么?

aov*_*ven 5 c# asynchronous task

阅读这个问题的答案促使我思考在等待任务抛出时异常情况发生了什么.所有"客户"都可以观察异常吗?我承认我可能会在这里混淆几件事; 这就是我要求澄清的原因.

我将介绍一个具体的场景......假设我有一个服务器,其中包含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.
}
Run Code Online (Sandbox Code Playgroud)

简而言之,它为任务提供了一些合理的时间来完成它首先完成的工作,然后回过头来报告正在进行的进展.这意味着有一个潜在的重要时间窗口,多个客户端可以观察到相同的任务失败.

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

Jer*_*ert 5

Task.WhenAny不会丢下自己。根据文档

当提供的任何任务完成时,返回的任务将完成。返回的任务将始终RanToCompletion 以其Result设置为要完成的第一个任务的状态结束。即使要完成的第一个任务以Canceledor 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);
}
Run Code Online (Sandbox Code Playgroud)

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

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