Sigurd Garshol
Sigurd Garshol

Reputation: 1544

Async method with no await vs Task.FromResult

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

Answers (1)

Marc Gravell
Marc Gravell

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:

  • return a reused static Task<bool> result each time
  • use ValueTask<bool> - which seems ideal here if you are returning synchronously a lot of the time

i.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

Related Questions