365

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?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
TX_
  • 5,056
  • 3
  • 28
  • 40
  • 7
    I think the only reason why you see this is because people learn by imitation and generally (if they don't need) they use the most simple solution they can find. So people see that code, use that code, they see it works and from now on, for them, that's the right way to do it... It's no use to await in that case – Fabio Marcolini Sep 30 '13 at 15:41
  • 20
    There's at least one important difference: **[exception propagation](http://stackoverflow.com/a/21082631/1768303)**. – noseratio Jan 13 '14 at 01:43
  • 1
    I dont understand it either, cant comprehend this entire concept at all, doesnt make any sense. From what I learned if a method has a return type, IT MUST have a return keyword, isnt it the rules of C# language? – monstro Mar 29 '18 at 15:57
  • 1
    @monstro the OP's question does have the return statement though? – David Klempfner Dec 11 '19 at 09:20
  • 1
    https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#prefer-asyncawait-over-directly-returning-task – Oliver Mar 11 '22 at 15:00
  • For anyone obsessed with this question, there is a recent YouTube video by Nick Chapsas [here](https://www.youtube.com/watch?v=aaC16Fv2zes "Settling the Biggest Await Async Debate in .NET"). – Theodor Zoulias Jan 07 '23 at 22:01
  • 1
    If `DoAnotherThingAsync` is written with async/await, then the async/await here is redundant and there's no different in exception propagation. `DoAnotherThingAsync` already implements the state machine that captures any exceptions thrown and embeds them in the returned Task. The only time you'd want to add async/await here is if the called method `DoAnotherThingAsync` does not use async/await and may throw exceptions. In that case, an exception could be thrown before the Task is returned, changing the required error handling approach. That's really the only difference. – Triynko Mar 03 '23 at 19:56

9 Answers9

262

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.

svick
  • 236,525
  • 50
  • 385
  • 514
  • 5
    For completeness, in first case you should return `foo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose());` – ghord Sep 23 '14 at 08:58
  • 11
    @ghord That wouldn't work, `Dispose()` returns `void`. You would need something like `return 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. – svick Sep 23 '14 at 09:05
  • 1
    @svick You're right, it should be more along the lines of `{ 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. – ghord Sep 23 '14 at 11:38
  • 2
    @ghord If you are on .Net 4.0 and you want to write asynchronous code, you should probably use [Microsoft.Bcl.Async](http://www.nuget.org/packages/Microsoft.Bcl.Async). And your code disposes of `Foo` only after the returned `Task` completes, which I don't like, because it unnecessarily introduces concurrency. – svick Sep 23 '14 at 12:47
  • 1
    @svick Your code waits until the task is finished too. Also, Microsoft.Bcl.Async is unusable for me due to dependency on KB2468871 and conflicts when using .NET 4.0 async codebase with proper 4.5 async code. – ghord Sep 23 '14 at 15:06
  • @svick Would it be safe to say to always use `async` and `await` to prevent this problem from happening? Because in the first example if the object `foo` isn't garbage collected and `Dispose` doesn't prevent `DoAnotherThingAsync` from finishing then this isn't easily identifiable as an exception isn't thrown. But wouldn't the drawback be that you're always starting a new thread regardless of whether its required or not if we follow this approach?? Apologies if I've misunderstood something. – user978139 Apr 17 '15 at 09:54
  • @user978139 Short answer: yes. Longer answer: yes, unless it affects performance in a way that matters to you. Though it's not about starting threads, [`async` doesn't do that](http://blog.stephencleary.com/2013/11/there-is-no-thread.html). – svick Apr 17 '15 at 13:33
  • An analogous case would be `lock(someObject){ return await SomeAsyncMethond(); }` – Jon Hanna Jun 09 '15 at 15:56
  • @JonHanna Fortunately, that doesn't compile. – svick Jun 09 '15 at 16:55
  • @svick Ah indeed. And yes, fortunately :) – Jon Hanna Jun 09 '15 at 21:19
  • one more thing to be noted is the call stack as mentioned in https://stackoverflow.com/a/26898323/5035500 – amit jha Oct 11 '17 at 14:57
  • The `return foo.DoAnotherThingAsync();` should check if foo is disposed and throw `ObjectDisposedException`. and if Foo implements: `IAsyncDisposable` the `await using (var foo = new Foo()) return await foo.DoAnotherThingAsync();` forces you to return await. – Wouter Apr 20 '21 at 18:09
139

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 20
    Could you add an explanation as to why the `await` is useful in the second case? Why not do `return SecondAwait(intermediate);` ? – Matt Smith Sep 30 '13 at 16:15
  • 2
    I have same question as Matt, wouldn't `return SecondAwait(intermediate);` achieve the goal in that case as well? I think `return await` is redundant here as well... – TX_ Sep 30 '13 at 16:31
  • 33
    @MattSmith That wouldn't compile. If you want to use `await` in the first line, you have to use it in the second one too. – svick Sep 30 '13 at 20:29
  • 2
    @svick as they just run sequentially, should they be changed to normal calls like `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? – cateyes Jul 17 '14 at 04:39
  • 2
    @cateyes I'm not sure what “overhead introduced by paralleling them” means, but the `async` version will use *less* resources (threads) than your synchronous version. – svick Jul 17 '14 at 09:29
  • 1
    @svick "That wouldn't compile. If you want to use await in the first line, you have to use it in the second one too." That is not true. The code will both compile and run just fine if you omit the second await and return SecondAwait directly. – Tom Lint Nov 23 '16 at 09:37
  • 10
    @TomLint [It really doesn't compile.](http://tryroslyn.azurewebsites.net/#K4Zwlgdg5gBAygTxAFwKYFsDcAoUlaIoYB0AKgBYBOqAhgCb5k0gDWIO2NARipTQMbIY/ADbMQMAMIwA3thgKYAB2BcRYfjG68BQ0sxYAeSMgB8MAGJhKKAIIgEEfgAoAlDkXLV6zduR9BGH1WQ158czhUfgB7CDp7RxcTGEgVZHd5RUyFFTUNLQcnIINQ/3CYAFkaSASnN2zZBs8ANxpKFIg0SnRUBho0GABeLQB3aqErG2RalwyASDnqZGBKCHgo2PjCpM7Ubt6wftQMzwBfbFOgA=) Assuming the return type of `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'". – svick Nov 23 '16 at 12:17
  • 3
    @svick You're right. Once you declare the method as 'async', you lose the ability to return a Task directly. I got confused by some code of mine which directly returned the Task from **another** async method instead of awaiting it. – Tom Lint Dec 07 '16 at 09:26
  • 1
    Stephen Cleary would you like to include an excerpt of your [Eliding Async and Await](https://blog.stephencleary.com/2016/12/eliding-async-await.html) blog post as part of this answer? I think that the paragraphs Efficiency, Pitfalls, Using and Exceptions would be helpful here, leaving out the AsyncLocal for being a too rare and specialized scenario. – Theodor Zoulias Nov 04 '22 at 01:02
29

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.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • 4
    As with Stephen's answer, I don't understand why would `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? – TX_ Sep 30 '13 at 16:33
  • 12
    @TX_ If you would want to remove `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. – Servy Sep 30 '13 at 16:38
  • 1
    @Servy, I guess the question is, why would we do `var result1 = await Task1Async(); return await Task2Async(result1)`, while we could just do `var result1 = await Task1Async(); return Task2Async(result1)`? I can't think of any reason, besides handling exceptions possibly thrown by `Task2Async` in this scope. – noseratio Sep 30 '13 at 19:19
  • 13
    @Noseratio Try the two. The first compiles. The second doesn't. The error message will tell you the problem. You won't be returning the proper type. When in an `async` method you don't return a task, you return the result of the task which will then be wrapped. – Servy Sep 30 '13 at 19:21
  • 3
    @Servy, of course - you're right. In the latter case we would return `Task` explicitly, while `async` dictates to return `Type` (which the compiler itself would turn into `Task`). – noseratio Sep 30 '13 at 19:33
  • 1
    @Servy You could do: return Task1Async.ContinueWith(task => Task2Async(task.Result)).Unwrap(). While not as clean, you don't have to incur the async overhead if you don't actually need the result of task1 – Itsik Feb 25 '16 at 22:37
  • 3
    @Itsik Well sure, `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. – Servy Feb 26 '16 at 02:33
21

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.

Andrew Arnott
  • 80,040
  • 26
  • 132
  • 171
20

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.

haimb
  • 381
  • 3
  • 4
  • 2
    Here is a good article about this: https://vkontech.com/exploring-the-async-await-state-machine-stack-traces-and-refactoring-pitfalls/ – Jonatan Dragon Jul 01 '21 at 11:55
12

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.

Andrew Arnott
  • 80,040
  • 26
  • 132
  • 171
5

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.

heltonbiker
  • 26,657
  • 28
  • 137
  • 252
  • 1
    "actual return type". Eh? async/await isn't changing return types. In your example `var task = DoSomethingAsync();` would give you a task, not `T` – jamesSampica Jan 05 '17 at 17:50
  • 5
    @Shoe I am not sure I understood well the `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;`. – heltonbiker Jan 05 '17 at 18:51
3

Another reason for why you may want to return await: The await syntax lets you avoid hitting a mismatch between Task<T> and ValueTask<T> 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<BlaBla>' to 'System.Threading.Tasks.ValueTask<BlaBla>'.

You'll get CS0030 too, if you try to explicitly typecast it.

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. :)

Sedat Kapanoglu
  • 46,641
  • 25
  • 114
  • 148
  • Calling this a conversion is a bit missleading I would say, since it has nothing to do with conversion as far as I understand. What you are doing above is getting the T result from the SubTask and then returning that result, not a task. So T is the same T, there are no any conversions. – KwahuNashoba Aug 17 '22 at 09:27
  • 2
    @KwahuNashoba Edited it now. How does it look? – Sedat Kapanoglu Aug 17 '22 at 23:40
  • 1
    Great! Thanks for being proactive :) – KwahuNashoba Aug 19 '22 at 10:30
1

Another problem with non-await method is sometimes you cannot implicitly cast the return type, especially with Task<IEnumerable<T>>:

async Task<List<string>> GetListAsync(string foo) => new();

// This method works
async Task<IEnumerable<string>> GetMyList() => await GetListAsync("myFoo");

// This won't work
Task<IEnumerable<string>> GetMyListNoAsync() => GetListAsync("myFoo");

The error:

Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Collections.Generic.List>' to 'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable>'

Luke Vo
  • 17,859
  • 21
  • 105
  • 181