I have a theoretical question to you. What happens if I await the Result of a Task inside another task? I want to know if my current system will work afterwards.
A task gets launched and does some stuff. At some point that task might need another one in order to process data which the current task is not able to process by itself. So I use await to ensure that the current task won't continue as long as he has not the result of the helper task. But what happens if the helper fails? Will the current task remain locked?
Can I avoid this deadlock somehow (without changing the system itself - task inside task)?
If you don't await the task or explicitly check for exceptions, the exception is lost. If you await the task, its exception is rethrown. As a best practice, you should always await the call. By default, this message is a warning.
See example below. Because await is only valid inside async functions and modules, which themselves are asynchronous and return promises, the await expression never blocks the main thread and only defers execution of code that actually depends on the result, i.e. anything after the await expression.
Wait and await - while similar conceptually - are actually completely different. Wait will synchronously block until the task completes. So the current thread is literally blocked waiting for the task to complete.
Nothing. The code ignores the task, so the task is ignored.
A task gets launched and does some stuff. At some point that task might need another one in order to process data which the current task is not able to process by itself. So I use await to ensure that the current task won't continue as long as he has not the result of the helper task. But what happens if the helper fails? Will the current task remain locked?
The core idea behind async
and await
is that asynchronous code works just about the same as synchronous code.
So, if you have synchronous code like this:
void HelperMethod()
{
throw new InvalidOperationException("test");
}
void DoStuff()
{
HelperMethod();
}
then you would expect DoStuff
to propagate the InvalidOperationException
from the helper method. Similarly, that's what happens with asynchronous code:
async Task HelperMethodAsync()
{
throw new InvalidOperationException("test");
}
async Task DoStuffAsync()
{
await HelperMethodAsync();
}
That is, DoStuffAsync
will also propagate the InvalidOperationException
.
Now, it doesn't work exactly the same way, of course, since it must be asynchronous, but the general idea is that all your control flow such as try
/catch
, for
loops, etc, all "just work" for asynchronous code very similarly to synchronous code.
What's actually going on is that when HelperMethod
ends with an InvalidOperationException
, the exception is caught and placed on the returned Task
, and the task is completed. When the await
in DoStuffAsync
sees that the task has completed, it examines its exceptions and re-raises the first one (in this case, there is only one, the InvalidOperationException
). And it re-raises it in a way that preserves the call stack on the exception. This in turn causes the Task
returned from DoStuffAsync
to be completed with that same exception.
So, under the covers async
and await
are doing a bit of work to ensure that you can just call other methods with await
and use try
/catch
the same way as you would in synchronous code. But most of the time you don't have to be aware of that.
It's really easy to test. For example:
[TestMethod, ExpectedException(typeof(Exception))]
public async Task DoFaultedTaskThrowOnAwait()
{
var task = Task.Factory.StartNew(() => { throw new Exception("Error 42"); });
await task;
}
[TestMethod, ExpectedException(typeof(AggregateException))]
public void DoFaultedTaskThrowOnWait()
{
var task = Task.Factory.StartNew(() => { throw new Exception("Error 42"); });
task.Wait();
}
Both tests pass, notice that Wait
throws an AggregateException
, and await
throws the Exception
.
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