Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

async Task method that doesn't need to await

Tags:

c#

async-await

I have a group of commands that inherit from a base class. The base class has the following declaration:

public virtual async Task Execute(object parameter){}

Inheriting classes provide an override of this method and not all of them await tasks. In these cases the compiler generates a warning:

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Is it correct to explicitly provide a task complete return value?

public override async Task Execute(object parameter) {
    //code containing no await statements...
    await Task.CompletedTask;
}
like image 444
jchristof Avatar asked Sep 14 '17 18:09

jchristof


2 Answers

You should avoid the use of the async keyword inside overridden methods which do not await on anything, and instead just return Task.CompletedTask:

public override Task Execute(object parameter) {
    //code containing no await statements...
    return Task.CompletedTask; // or Task.FromResult(0) for older .NET versions
}

This is one of the (few) use cases for such "mocked" tasks, as you may understand by looking at this question about Task.FromResult. Those considerations are still valid for Task.CompletedTask.

like image 83
Federico Dipuma Avatar answered Sep 28 '22 08:09

Federico Dipuma


As a counterpoint, there is a difference in the way exceptions are handled.

If an exception is raised from the synchronous code in the non-async version, then that exception is raised directly to the caller (very unusual for methods with asynchronous signatures).

If an exception is raised from the synchronous code in the async version, then that exception is captured and placed on the returned task (which is the expected behavior for methods with asynchronous signatures).

So, if the method is called as such, the different implementations would have different exception semantics:

var task = Execute(parameter); // non-async exceptions raised here
...
await task; // async exceptions raised here

Most of the time, though, the method is called and immediately awaited, so those two semantics merge together:

await Execute(parameter); // both async and non-async exceptions raised here

It probably won't matter, but I believe it's an important distinction to be aware of.

If the exception semantics are important to you, then you do want to use async to generate a state machine around your synchronous code, and you can disable the warning in your code:

#pragma warning disable 1998 // use async keyword to capture exceptions
public override async Task Execute(object parameter) {
#pragma warning restore 1998
  //code containing no await statements...
}
like image 25
Stephen Cleary Avatar answered Sep 28 '22 08:09

Stephen Cleary