当Task.WaitAll(t1, t2)
的执行与t2
同步时,t1
会有什么期望,但t1
会在与t2
同步的点之前引发异常{1}}?在这种情况下,t2
永远不会完成。但是,因为t1
会引发异常,我希望Task.WaitAll(t1,t2)
返回聚合异常,表示其中一个任务失败。
但事实并非如此。事实证明Task.WaitAll
等待着永远等待。
我可以争论这种行为,Task.WaitAll
做它声称要等待所有任务返回的行为,即使其中一个已经抛出异常。虽然我不喜欢它,但只要我意识到它,它对我来说仍然没问题。
但是,我的问题是,是否有Task.WaitAll
的替代API,"等待所有任务完成的行为,除非其中一个任务引发异常" ?我想这是我大部分时间都需要的行为。
我最初使用TaskCompletionSource<>
进行同步。但这对我想做的事情来说并不重要。所以我用一个简短的民意调查来改变它。
我写了一个F#等效程序(见下文),发现它实际上确实有我预期的行为。如上所述,Task.WhenAll
或Task.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
答案 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
方法WhenAll
和WhenAny
,并释放当前线程以进行其他工作。现在Wait*
方法将阻止你的线程而不做任何事情。此外,如果其中一个任务失败,您应该使用取消令牌来取消所有任务。
修改强>:
如果我们只讨论未处理的例外,那么请使用TaskScheduler.UnObservedTaskException
事件,以便取消正在运行的所有其他任务。