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.WhenAll
或Task.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
方法WhenAll
和WhenAny
,以及免费的当前线程以进行额外的工作。现在,Wait*
方法将阻止你的线程而不做任何事情。此外,如果其中一个任务失败,您应该使用取消令牌来取消所有任务。
编辑:
如果我们只讨论未处理的异常,那么使用TaskScheduler.UnObservedTaskException
事件,这样您就可以取消正在运行的所有其他任务。
以上是关于Task.WaitAll在一个任务抛出时挂起,另一个任务与第一个任务同步的主要内容,如果未能解决你的问题,请参考以下文章
带有异步 lambda 和 Task.WaitAll 的 Task.Factory.StartNew