Task.WaitAll在一个任务抛出时挂起,另一个任务与第一个任务同步

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Task.WaitAll在一个任务抛出时挂起,另一个任务与第一个任务同步相关的知识,希望对你有一定的参考价值。

Task.WaitAll(t1, t2)的执行与t2同步时,t1应该期待什么,但是t1在它与t2同步的点之前抛出异常?在这种情况下,很明显t2永远不会完成。然而,由于t1抛出异常,我期望Task.WaitAll(t1,t2)返回聚合异常,表明其中一个任务失败。

然而,这种情况并非如此。事实证明,Task.WaitAll等待着永远。

我可以争论这种行为,Task.WaitAll做了它声称要等待所有任务回来的事情,即使其中一个人已经抛出异常。虽然我不喜欢它,但只要我知道它,它对我来说仍然没问题。

但是,我的问题是,是否存在替代Task.WaitAll的API,其行为是“等待所有任务完成,除非其中一个任务引发异常”?我想这是我大部分时间都需要的行为。

Edit 1

我最初使用TaskCompletionSource<>进行同步。但这对我想做的事情并不重要。所以我用一个初步的民意调查改变了它。

Edit 2

我写了一个F#等效程序(见下文),发现它实际上确实有我预期的行为。如上所述,Task.WhenAllTask.WaitAll等待所有任务完成,即使它们的一部分失败。然而,Async.Parallel,相当于F#中的WhenAll,在任何子任务失败时急切地失败,如the document所描述并在下面的程序中测试:

如果所有子计算都成功,则会将结果数组传递给成功继续。如果任何子计算引发异常,则整体计算将触发异常,并取消其他异常。整体计算将在执行子计算时响应取消。如果取消,计算将取消任何剩余的子计算,但仍将等待其他子计算完成。

是否存在与F#的Async.Parallel相同的C#,因为在等待所有任务完成时,只要子任务失败,它就会抛出并抛出?

我使用的示例:

public void Test()
{
    bool t1done = false;

    Task t1 = Task.Run(() =>
    {
        DoThing(); // throw here
        t1done = true;
    });

    Task t2 = Task.Run(async () =>
    {
        // polling if t1 is done.
        // or equivelantly: SpinWait.SpinUntil(() => t1done);
        while (!t1done) await Task.Delay(10);
    });

    // t2 will never finish, but t1 threw. So I expect Task.WaitAll to throw rather than blindly wait
    // for t2 EVEN THOUGH t1 threw already.
    Task.WaitAll(t1, t2); // never returns
    // or this could be an async wait, but still this function would never return:
    // await Task.WhenAll(t1,t2);

    Console.WriteLine("done");
}

void DoThing()
{
    throw new InvalidOperationException("error");
}

F#“​​Equivalence”确实具有我期望的行为:

[<EntryPoint>]
let main argv = 
    let mutable ready : bool = false
    let task1 = async {
        failwith "foo"
        ready <- true
    }

    let task2 = async {
        while not ready do do! Async.Sleep 100
    }

    [| task1; task2 |] 
    |> Async.Parallel // Equivelant to Task.WhenAll() - combining task1 and task1 into a single Async, 
                      // but it throws instead of waiting infinately.
    |> Async.RunSynchronously // run task
    |> ignore

    0
答案

没有我所知道的等效API。

我建议你确保任务t2可以完成!然后WaitAll()将按照您的预期从t1抛出异常,它可以让您更好地控制任务t2发生的事情。

假设你的t2任务在开始轮询t1完成之前完成了一大堆其他工作,以便从并行运行的两个任务中受益。在t1抛出异常之前,你怎么知道它完成了多少工作?

WaitAll并不关心任务如何完成,(Canceled,Faulted或RanToCompletion)只是它是其中之一。

当您可以检查任务本身的结果时,实际上不需要变量t1done:

Task t1 = Task.Run(() =>
{
    throw null;       
});

Task t2 = Task.Run(async () =>
{
    // check whether task 1 is finished yet 
    while (!t1.IsCompleted) await Task.Delay(10);
    if (t1.Status == TaskStatus.RanToCompletion)
    {
        // we know that t1 finished successfully and did not throw any 
        // error.           
    }
    else
    {
        Console.WriteLine("We know that t1 did not run to completion");
    }
});

try
{
    Task.WaitAll(t1, t2); 
    // this will now throw the exception from t1 because t2 can also finish
}
catch (AggregateException)
{

}

// For interest sake, you can now view the status of each task.            
Console.WriteLine(t1.Status);
Console.WriteLine(t2.Status);
Console.WriteLine("Finished");

现在,这是否是您解决方案的最佳设计我不能说。你有没有看过Task Continuations?因为如果t2除了等待t1完成之外什么都不做,那么有更好的选择......但这应该回答你直接问的问题。

另一答案

由于t1抛出一个异常,我期望Task.WaitAll(t1, t2)返回聚合异常,表明其中一个任务失败

你为什么期待呢? WaitAll意味着等待,嗯,你的所有任务,显然不是这样。

您的代码包含不一致性:您没有失败(或取消)任务完成源,您应该这样做:

var ex = new InvalidOperationException("error");
if (shouldThrow)
{
    // SetCanceled 
    // t1done.SetCanceled();

    // or SetException
    // t1done.SetException(ex);

    throw ex;
}
t1done.SetResult(true);

是否存在替代Task.WaitAll的API,其行为是“等待所有任务完成,除非其中一个任务引发异常”?

您可以使用WaitAny方法进行循环,这样您就可以检查所有已完成任务的状态,并在发生任何异常时抛出。

PS:考虑切换到async方法WhenAllWhenAny,以及免费的当前线程以进行额外的工作。现在,Wait*方法将阻止你的线程而不做任何事情。此外,如果其中一个任务失败,您应该使用取消令牌来取消所有任务。

编辑:

如果我们只讨论未处理的异常,那么使用TaskScheduler.UnObservedTaskException事件,这样您就可以取消正在运行的所有其他任务。

以上是关于Task.WaitAll在一个任务抛出时挂起,另一个任务与第一个任务同步的主要内容,如果未能解决你的问题,请参考以下文章

带有异步 lambda 和 Task.WaitAll 的 Task.Factory.StartNew

Task.WaitAll代替WaitHandle.WaitAll

SSIS 数据流任务在执行预执行阶段时挂起

Jenkins 从 Git 获取代码时挂起

HttpClient - 任务被取消?

Spark 2.1 在读取大量数据集时挂起