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