当一个任务引发另一个任务与第一个任务同步时,Task.WaitAll挂起 [英] Task.WaitAll hang when one task throws and another syncs with the first task

查看:87
本文介绍了当一个任务引发另一个任务与第一个任务同步时,Task.WaitAll挂起的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

t2的执行与t1同步但t1在其同步到t2的点之前引发异常时,从Task.WaitAll(t1, t2)会有什么期望?在这种情况下,很明显t2将永远不会完成.但是由于t1引发异常,我希望Task.WaitAll(t1,t2)会返回汇总异常,指示任务之一失败.

但是事实并非如此.事实证明,Task.WaitAll永远挂起等待.

对于这种行为,我可以辩称Task.WaitAll会执行它声称的等待所有任务返回的操作,即使其中一个任务已经引发异常也是如此.尽管我不喜欢它,但对我来说仍然可以,只要我知道它即可.

但是,我的问题是,Task.WaitAll是否有替代API,其行为为等待所有任务完成,除非其中一个任务抛出异常" ?我想这是我大多数时候需要的行为.

编辑1

我最初使用TaskCompletionSource<>进行同步.但这对我想提出的观点无关紧要.因此,我通过简单的投票对其进行了更改.

编辑2

我写了一个等效于F#的程序(见下文),发现它实际上确实具有我所期望的行为.如上所述,Task.WhenAllTask.WaitAll等待 all 任务完成,即使其中一部分失败.但是,如

F#等效"确实具有我期望的行为:

[<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并不关心任务如何完成(已取消,出现故障或RanToCompletion),只是它是其中之一.

当您可以检查Task本身的结果时,实际上不需要变量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");

现在我不能说这是否是最适合您解决方案的设计.您是否研究了任务继续?因为如果t2除了等待t1完成以外什么都不做,那么还有更好的选择...但是这应该可以直接回答您提出的问题.

What should be expected from Task.WaitAll(t1, t2), when t2's execution syncs with t1, but t1 throws an exception before the point it syncs to t2? In this case, it's obvious that t2 will never finish. However since t1 throws an exception, I expected Task.WaitAll(t1,t2) to return with aggregated exception indicating one of the task fails.

However this is not the case. It turns out that Task.WaitAll hangs waiting forever.

I can argue for this behavior that Task.WaitAll does what it claims to wait for ALL tasks to come back, EVEN if one of them already threw an exception. Although I don't prefer it, it is still fine to me as long as I'm aware of it.

However, my question is, is there an alternative API to Task.WaitAll, with the behavior of "waiting for all task to finish, unless one of the task threw an exception"? I imagine this is the behavior I would need most of the time.

Edit 1

I originally used TaskCompletionSource<> for the synchronization. But that's immaterial to the point I want to make. So I changed it with a rudimentary polling.

Edit 2

I wrote an F# equivalent program (see below) and found that it actually does have the behavior as I expected. As mentioned above, Task.WhenAll or Task.WaitAll waits for all tasks to complete, even if a subset of them failed. However, Async.Parallel, as the equivalent of WhenAll in F#, fails eagerly when any sub-task fails, as described by the document and tested in the program below:

If all child computations succeed, an array of results is passed to the success continuation. If any child computation raises an exception, then the overall computation will trigger an exception, and cancel the others. The overall computation will respond to cancellation while executing the child computations. If cancelled, the computation will cancel any remaining child computations but will still wait for the other child computations to complete.

Is there a C# equivalence to F#'s Async.Parallel, in that when waiting for all task to finish, it bails and throws whenever a sub-task fails?

Example that I used:

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" which does have the behavior that I expected:

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

解决方案

There is no equivalent API that I am aware of.

I would suggest that you ensure Task t2 can finish! WaitAll() will then throw the exception from t1 as you expect, and it gives you more control over what happens to Task t2.

Assuming your t2 task does a whole heap of other work before it begins polling t1 for completion to benefit from the two tasks running in parallel. How do you know how much of that work it has completed before t1 throws the exception?

WaitAll does not care how the tasks complete, (Cancelled, Faulted or RanToCompletion) only that it is one of those.

There is really no need for the variable t1done, when you can be checking the result of the Task itself:

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");

Now whether this is the best design for your solution or not I cannot say. Have you looked into Task Continuations? Because if t2 does nothing other than wait for t1 to complete, there are better options... but this should answer the question you have asked directly.

这篇关于当一个任务引发另一个任务与第一个任务同步时,Task.WaitAll挂起的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆