Reputation: 1544
Consider the following interface:
public interface IProvider
{
Task<bool> Contains(string key);
}
This is implementation satisfies Visual Studio
public Task<bool> Contains(string key)
{
return Task.FromResult(false);
}
This implementation is convenient to write and would seem to achieve the same thing:
public async Task<bool> Contains(string key)
{
return false;
}
However, Visual Studio throws a hissy-fit and insists:
This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await TaskEx.Run(...)' to do CPU-bound work on a background thread.
I'd love to just ignore that warning and avoid using Task.FromResult(...)
.
Are there any negative consequences to using the latter option?
Upvotes: 9
Views: 3675
Reputation: 1062640
The reason for that "hissy fit" is that the compiler needs to do a lot of work to present a task that works in all the expected right ways here, which you can see by compiling and decompiling it
Task.FromResult
is cleaner, but may still have overhead - IIRC there are some scenarios where a Task.FromResult
might work efficiently here (returning the same object each time), but I wouldn't rely on it.
There are 2 pragmatic reliable approaches:
Task<bool>
result each timeValueTask<bool>
- which seems ideal here if you are returning synchronously a lot of the timei.e.
private readonly static Task<bool> s_False = Task.FromResult(false);
public Task<bool> Contains(string key, string scope)
{
return s_False ;
}
or
public ValueTask<bool> Contains(string key, string scope)
{
return new ValueTask<bool>(false);
}
Note: the second of these may not be possible in this case, since you didn't define the interface. But: if you ever are designing an interface that needs to allow async usage but which may actually be sync: consider using ValueTask<T>
as the exchange type, not Task<T>
.
The generated C# of:
public async System.Threading.Tasks.Task<bool> Contains(string key, string scope)
{
return false;
}
is something like:
[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <Contains>d__0 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<bool> <>t__builder;
private void MoveNext()
{
bool result;
try
{
result = false;
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult(result);
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
<>t__builder.SetStateMachine(stateMachine);
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
[AsyncStateMachine(typeof(<Contains>d__0))]
public Task<bool> Contains(string key, string scope)
{
<Contains>d__0 stateMachine = default(<Contains>d__0);
stateMachine.<>t__builder = AsyncTaskMethodBuilder<bool>.Create();
stateMachine.<>1__state = -1;
AsyncTaskMethodBuilder<bool> <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
Upvotes: 19