I will show you some reasons to return a task instead of awaiting it.
Imaging you have the following case:
private static async Task WaitAsync() => await Task.Delay(100);
First question that comes in my mind is why not returning directly the task instead of awaiting on it. Of course the first answer I have is - because it won't generate redundant state machine. So I've googled about this topic and I found just the same reason + an explanation that the code will be faster. It's is logically, in most cases less code means faster execution. This is a fantastic post on this topic. One interesting point described in the article is that you haven't to return the task in case of disposable objects:
public async Task FooBar()
{
using (var foo = new Foo())
{
await SomethingAsync(foo);
}
}
This is another example:
public Task GetDataAsync()
{
using (var httpClient = new HttpClient())
{
return httpClient.GetStringAsync("https://google.com");
}
}
In this way httpClient
will be disposed before the the async operation is completed. Using different types the async operation might be sent/start executed, but the callback/response won't be handled because the caller object is disposed. It cloud be possible to finish the async operation even the caller object is disposed, but keep in mind that this is implementation specific and it's better to not relay on this. So a good rule of thumb: don't use async/await when you play with disposable objects.
However, thinking twice got me another reason to return the task instead of awaiting it - you are escaping from the deadlock hell. You can read more about it in my previous post - Stop using .Result or .Wait() on tasks and why it's.
So imaging we have the following case:
private void button_Click(object sender, RoutedEventArgs e)
{
Delay().Wait();
MessageBox.Show("Delay called.");
Delay2().Wait();
MessageBox.Show("Delay2 called.");
Delay3().Wait();
MessageBox.Show("Delay3 called.");
}
private static Task Delay() => Task.Delay(100);
private static async Task Delay2() => await Task.Delay(100).ConfigureAwait(false);
private static async Task Delay3() => await Task.Delay(100);
When running the code you will never end with "Delay3 called." message because you have deadlocked your application. In order to prevent the deadlock in Delay2
method we are using .ConfigureAwait(false)
which is described as a hack by Stephen Cleary. This is why it's better to return the task directly like in Delay
method.
Conclusion
These are the pros and cons of returning a task instead of awaiting it:
Pros
- There is no context switch, so it's deadlock free and you don't need to use hacks -
.ConfigureAwait(false)
- It's ~16 times faster (but the difference is ~20ns)
- Less code is generated => less work for JIT compiler
- Easier to decompile, read and debug
Cons
- You should be very carful in case of disposable objects