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

时间:2017-12-14 19:40:53

标签: c# multithreading exception-handling async-await task-parallel-library

Task.WaitAll(t1, t2)的执行与t2同步时,t1会有什么期望,但t1会在与t2同步的点之前引发异常{1}}?在这种情况下,t2永远不会完成。但是,因为t1会引发异常,我希望Task.WaitAll(t1,t2)返回聚合异常,表示其中一个任务失败。

但事实并非如此。事实证明Task.WaitAll等待着永远等待。

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

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

编辑1

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

编辑2

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

  

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

是否存在与F#&#39; s 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#&#34;等效&#34;这确实有我期望的行为

[<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

2 个答案:

答案 0 :(得分:1)

没有我所知道的等效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完成之外什么都不做,那么有更好的选择......但这应该回答你直接提出的问题。

答案 1 :(得分:-1)

  

因为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,&#34;等待所有任务完成的行为,除非其中一个任务引发异常&#34;?

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

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

修改

如果我们只讨论未处理的例外,那么请使用TaskScheduler.UnObservedTaskException事件,以便取消正在运行的所有其他任务。