Is there any scenario where writing method like this:
public async Task<SomeResult> DoSomethingAsync()
{
// Some synchronous code might or might not be here... //
return await DoAnotherThingAsync();
}
instead of this:
public Task<SomeResult> DoSomethingAsync()
{
// Some synchronous code might or might not be here... //
return DoAnotherThingAsync();
}
would make sense?
Why use return await
construct when you can directly return Task<T>
from the inner DoAnotherThingAsync()
invocation?
I see code with return await
in so many places, I think I might have missed something. But as far as I understand, not using async/await keywords in this case and directly returning the Task would be functionally equivalent. Why add additional overhead of additional await
layer?
There is one sneaky case when return
in normal method and return await
in async
method behave differently: when combined with using
(or, more generally, any return await
in a try
block).
Consider these two versions of a method:
Task<SomeResult> DoSomethingAsync()
{
using (var foo = new Foo())
{
return foo.DoAnotherThingAsync();
}
}
async Task<SomeResult> DoSomethingAsync()
{
using (var foo = new Foo())
{
return await foo.DoAnotherThingAsync();
}
}
The first method will Dispose()
the Foo
object as soon as the DoAnotherThingAsync()
method returns, which is likely long before it actually completes. This means the first version is probably buggy (because Foo
is disposed too soon), while the second version will work fine.
If you don't need async
(i.e., you can return the Task
directly), then don't use async
.
There are some situations where return await
is useful, like if you have two asynchronous operations to do:
var intermediate = await FirstAsync();
return await SecondAwait(intermediate);
For more on async
performance, see Stephen Toub's MSDN article and video on the topic.
Update: I've written a blog post that goes into much more detail.
await
is useful in the second case? Why not do return SecondAwait(intermediate);
?
return SecondAwait(intermediate);
achieve the goal in that case as well? I think return await
is redundant here as well...
await
in the first line, you have to use it in the second one too.
var intermediate = First(); return Second(intermediate)
to avoid the overhead introduced by paralleling then. The async calls aren't necessary in this case, are they?
SecondAwait
is `string, the error message is: "CS4016: Since this is an async method, the return expression must be of type 'string' rather than 'Task<string>'".
The only reason you'd want to do it is if there is some other await
in the earlier code, or if you're in some way manipulating the result before returning it. Another way in which that might be happening is through a try/catch
that changes how exceptions are handled. If you aren't doing any of that then you're right, there's no reason to add the overhead of making the method async
.
return await
be necessary (instead of just returning the task of child invocation) even if there is some other await in the earlier code. Could you please provide explanation?
async
then how would you await the first task? You need to mark the method as async
if you want to use any awaits. If the method is marked as async
and you have an await
earlier in code, then you need to await
the second async operation for it to be of the proper type. If you just removed await
then it wouldn't compile as the return value wouldn't be of the proper type. Since the method is async
the result is always wrapped in a task.
async
method you don't return a task, you return the result of the task which will then be wrapped.
Task<Type>
explicitly, while async
dictates to return Type
(which the compiler itself would turn into Task<Type>
).
async
is just syntactic sugar for explicitly wiring up continuations. You don't need async
to do anything, but when doing just about any non-trivial asynchronous operation it's dramatically easier to work with. For example, the code you provided doesn't actually propagate errors as you'd want it to, and doing so properly in even more complex situations starts to become quite a lot harder. While you never need async
, the situations I describe are where it's adding value to use it.
Another case you may need to await the result is this one:
async Task<IFoo> GetIFooAsync()
{
return await GetFooAsync();
}
async Task<Foo> GetFooAsync()
{
var foo = await CreateFooAsync();
await foo.InitializeAsync();
return foo;
}
In this case, GetIFooAsync()
must await the result of GetFooAsync
because the type of T
is different between the two methods and Task<Foo>
is not directly assignable to Task<IFoo>
. But if you await the result, it just becomes Foo
which is directly assignable to IFoo
. Then the async method just repackages the result inside Task<IFoo>
and away you go.
Task<>
is invariant.
If you won't use return await you could ruin your stack trace while debugging or when it's printed in the logs on exceptions.
When you return the task, the method fulfilled its purpose and it's out of the call stack. When you use return await
you're leaving it in the call stack.
For example:
Call stack when using await: A awaiting the task from B => B awaiting the task from C
Call stack when not using await: A awaiting the task from C, which B has returned.
Making the otherwise simple "thunk" method async creates an async state machine in memory whereas the non-async one doesn't. While that can often point folks at using the non-async version because it's more efficient (which is true) it also means that in the event of a hang, you have no evidence that that method is involved in the "return/continuation stack" which sometimes makes it more difficult to understand the hang.
So yes, when perf isn't critical (and it usually isn't) I'll throw async on all these thunk methods so that I have the async state machine to help me diagnose hangs later, and also to help ensure that if those thunk methods ever evolve over time, they'll be sure to return faulted tasks instead of throw.
This also confuses me and I feel that the previous answers overlooked your actual question:
Why use return await construct when you can directly return Task from the inner DoAnotherThingAsync() invocation?
Well sometimes you actually want a Task<SomeType>
, but most time you actually want an instance of SomeType
, that is, the result from the task.
From your code:
async Task<SomeResult> DoSomethingAsync()
{
using (var foo = new Foo())
{
return await foo.DoAnotherThingAsync();
}
}
A person unfamiliar with the syntax (me, for example) might think that this method should return a Task<SomeResult>
, but since it is marked with async
, it means that its actual return type is SomeResult
. If you just use return foo.DoAnotherThingAsync()
, you'd be returning a Task, which wouldn't compile. The correct way is to return the result of the task, so the return await
.
var task = DoSomethingAsync();
would give you a task, not T
async/await
thing. To my understanding, Task task = DoSomethingAsync()
, while Something something = await DoSomethingAsync()
both work. The first gives you the task proper, while the second, due to the await
keyword, gives you the result from the task after it completes. I could, for example, have Task task = DoSomethingAsync(); Something something = await task;
.
Another reason for why you may want to return await
: The await
syntax supports automatic conversion between Task<T>
and ValueTask<T>
return types. For example, the code below works even though SubTask method returns Task<T>
but its caller returns ValueTask<T>
.
async Task<T> SubTask()
{
...
}
async ValueTask<T> DoSomething()
{
await UnimportantTask();
return await SubTask();
}
If you skip await on the DoSomething()
line, you'll get a compiler error CS0029:
Cannot implicitly convert type 'System.Threading.Tasks.Task
You'll get CS0030 if you try to explicitly typecast it. Don't bother.
This is .NET Framework, by the way. I can totally foresee a comment saying "that's fixed in .NET hypothetical_version", I haven't tested it. :)
Success story sharing
foo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose());
Dispose()
returnsvoid
. You would need something likereturn foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; });
. But I don't know why would you do that when you can use the second option.{ var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; }
. The use case is pretty simple: if you are on .NET 4.0 (like most), you can still write async code this way which will work nicely called from 4.5 apps.Foo
only after the returnedTask
completes, which I don't like, because it unnecessarily introduces concurrency.