Reading through answers to this question prompted me to think about what happens exception-wise when the awaited task throws. Do all "clients" get to observe the exception? I admit I may be confusing a couple of things here; that's the reason I'm asking for clarification.
I'll present a concrete scenario... Let's say I have a server with a global collection of long-running Task
instances, started by clients. After starting one or more tasks, a client can query their progress and retrieve results when they become available, as well as any errors that might occur.
Tasks themselves may perform very different business-specific things - normally, no two are quite the same. However, if one of the clients does attempt to start the same task as another had started previously, the server should recognize this and "attach" the second client to the existing task instead of spooling up a new copy.
Now, each time any client queries the status of the task it's interested in, it does something along these lines:
var timeout = Task.Delay(numberOfSecondsUntilClientsPatienceRunsOut);
try
{
var result = await Task.WhenAny(timeout, task);
if (result == timeout)
{
// SNIP: No result is available yet. Just report the progress so far.
}
// SNIP: Report the result.
}
catch (Exception e)
{
// SNIP: Report the error.
}
In short, it gives the task some reasonable time to finish what it's doing first, then falls back to reporting the on-going progress. This means there's a potentially significant window of time in which multiple clients could observe the same task failing.
My question is: if the task happens to throw during this window, is the exception observed (and handled) by all clients?
Task.WhenAny
will not throw itself. Per the documentation:
The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the
RanToCompletion
state with its Result set to the first task to complete. This is true even if the first task to complete ended in theCanceled
orFaulted
state.
You will get back either timeout
or task
.
If the result is task
, and you await that (or get Task.Result
), and task
has faulted, then that will throw. It doesn't matter how many callers do that, or when they do it -- attempting to get the result of a faulted task always throws. Simple code to demonstrate this:
var t = Task.Run(() => throw new Exception(DateTime.Now.Ticks.ToString()));
try {
await t;
} catch (Exception e) {
Console.WriteLine(e.Message);
}
await Task.Delay(1000);
try {
await t;
} catch (Exception e) {
Console.WriteLine(e.Message);
}
This will print the same timestamp, twice. The task only runs once, and only has one result, which produces an exception every time you try to get it. If you like you can mix in different threads or parallel calls, this doesn't change the outcome.
Note that in the case of a timeout, there is still the basic possibility of a race condition: two different tasks/threads, both awaiting the same task, may get different results on await Task.WhenAny(timeout, task)
, based on which task they observe to complete first. In other words, even if await Task.WhenAny(timeout, task) == timeout
, task
can still have faulted at any point between .WhenAny()
deciding it was done and control eventually returning to you. This is to be expected, and your code should handle this (on the next round of waiting, .WhenAny()
would return immediately with the faulted task).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With