So you're implementing this async handler or interface method that doesn't have anything to await, and you write something like
async Task<bool> HandleAsync()
{
DoSomethingSynchronous();
return true;
}
But now the C# compiler emits a stern warning: "CS1998: you're using async
, so you should be awaiting stuff! find something to await!"* Well, you have nothing to await, but you must still return Task. You remove async
and return Task.FromResult(true)
instead, as top-rated answers by high-reputation members on Stackoverflow recommend. Job done.
Task<bool> HandleAsync()
{
DoSomethingSynchronous();
return Task.FromResult(true);
}
Or is it?
As long as HandleAsync
is always called and awaited immediately, all is fine.
var success = await HandleAsync(); // This is fine.
But a dark threat lurks in the shadows. Sometimes a try-catch fails to catch an exception.
var task0 = a.HandleAsync();
var task1 = b.HandleAsync();
try
{
await Task.WhenAll(new[] { task0, task1 });
}
catch (Exception ex)
{
// this handler does nothing and the exception escapes! What gives??
}
Because HandleAsync
is actually synchronous, any exception propagates as soon as it's called, not when it's awaited. This is surprising to most C# programmers. A synchronous method that returns a Task is not what you expect, especially when it's called SomethingAsync. Surprising, unexpected behavior leads to bugs. Bugs lead to unhappy customers and developers alike.
So what should we do instead?
One option would be to disable warning CS1998, but it may point out cases where a method just shouldn't return a Task in the first place. Probably the best thing would be to mark the function as async and await Task.FromResult
:
async Task<bool> HandleAsync()
{
DoSomethingNotAsync();
return await Task.FromResult(true);
}
Or if it's a plain Task
, awaiting Task.CompletedTask
.
async Task HandlerAsync()
{
DoSomethingNotAsync();
await Task.CompletedTask;
}
Is this slower? Not much - awaiting a completed task does almost nothing, and the runtime might be able to recognize this common pattern and elide all the overhead - but choosing the async-looking-but-actually-synchronous behavior for hypothetical performance reasons firmly falls in the category "evil premature optimization", in my opinion.
*Actually, it says
warning CS1998: 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.
Top comments (0)