I have a interface which exposes some async methods. More specifically it has methods defined which return either Task or Task<T>. I am using the async/await keywords.
I am in the process of implementing this interface. However, in some of these methods this implementation doesn't have anything to await. For that reason I am getting the compiler warning "This async method lacks 'await' operators and will run synchronously..."
I understand why I am getting the error but am wondering whether I should do anything about them in this context. It feels wrong to ignore compiler warnings.
I know I can fix it by awaiting on a Task.Run but that feels wrong for a method that is only doing a few inexpensive operations. It also sounds like it will add unneeded overhead to the execution but then I am also not sure if that is already there because the async keyword is present.
Should I just ignore the warnings or is there a way of working around this that I am not seeing?
The async keyword is merely an implementation detail of a method; it isn't part of the method signature. If one particular method implementation or override has nothing to await, then just omit the async keyword and return a completed task using Task.FromResult<TResult>:
public Task<string> Foo() // public async Task<string> Foo()
{ // {
Baz(); // Baz();
return Task.FromResult("Hello"); // return "Hello";
} // }
If your method returns Task instead of Task<TResult>, then you can return a completed task of any type and value. Task.FromResult(0)
seems to be a popular choice:
public Task Bar() // public async Task Bar()
{ // {
Baz(); // Baz();
return Task.FromResult(0); //
} // }
Or, as of .NET Framework 4.6, you can return Task.CompletedTask:
public Task Bar() // public async Task Bar()
{ // {
Baz(); // Baz();
return Task.CompletedTask; //
} // }
It's perfectly reasonable that some "asynchronous" operations complete synchronously, yet still conform to the asynchronous call model for the sake of polymorphism.
A real-world example of this is with the OS I/O APIs. Asynchronous and overlapped calls on some devices always complete inline (writing to a pipe implemented using shared memory, for example). But they implement the same interface as multi-part operations which do continue in the background.