异步方法无需等待 vs Task.FromResult。

8

考虑以下接口:

public interface IProvider
{
    Task<bool> Contains(string key);
}

这个实现满足了Visual Studio的要求。

public Task<bool> Contains(string key)
{
    return Task.FromResult(false);
}

这个实现方法 写起来很方便,看起来似乎可以达到同样的效果:
public async Task<bool> Contains(string key)
{
    return false;
}

然而,Visual Studio会抛出一些错误并坚称:
这个异步方法缺少'await'运算符,将同步运行。考虑使用'await'运算符等待非阻塞API调用,或者使用'TaskEx.Run(...)'在后台线程上执行CPU绑定工作。
我很想忽略这个警告并避免使用Task.FromResult(...)。但是,使用后面的选项会有什么负面影响吗?

1
顺便问一下,这段代码的意义是什么?如果你不打算异步运行任何东西,为什么要使用任务?为什么不直接编写一个 public bool Contains(..) - Panagiotis Kanavos
一般来说,这是所谓的“异步超同步”反模式,应该避免使用。我们并没有真正看到任何async操作,为什么你想为了你的“小方便”而创建一个状态机呢?你可以自己简单地返回一个Task - Fabjan
3
我认为这是使用Task.FromResult的情况。当接口允许异步调用者,但实现是同步的时候。 - ChristianMurschall
2
@PanagiotisKanavos - 如果有人定义了IProvider并且您正在尝试实现它,那么您就没有太多选择。 - Damien_The_Unbeliever
@ChristianMurschall已经解决了签名不匹配的问题,这只是示例代码,而不是真实世界中的实现。 :) 谢谢你承认这是一个合法的问题,而不是把它作为“你一开始就有错”的问题来否定它。 :) - Sigurd Garshol
显示剩余2条评论
1个回答

18
“hissy fit”的原因是编译器需要做大量的工作,以呈现一个在所有预期的正确方式下都能正常工作的任务,您可以通过编译和反编译来看到这一点by compiling and decompiling itTask.FromResult更加简洁,但可能仍然存在开销——如果我没记错的话,在某些情况下,Task.FromResult 可能会有效地工作(每次返回相同的对象),但我不会依赖它。
有两种实用可靠的方法:
  • 每次返回重复使用的静态 Task<bool> 结果
  • 使用 ValueTask<bool>——如果您大部分时间都在同步返回,则似乎在这里是理想的
即。
private readonly static Task<bool> s_False = Task.FromResult(false);
public Task<bool> Contains(string key, string scope)
{
    return s_False ;
}

或者

public ValueTask<bool> Contains(string key, string scope)
{
    return new ValueTask<bool>(false);
}

注意:在这种情况下,第二个可能不可行,因为您没有定义接口。但是:如果您曾经设计需要允许异步使用但实际上可能是同步的接口,请考虑使用 ValueTask<T> 作为交换类型,而不是 Task<T>

生成的 C# 代码如下:

public async System.Threading.Tasks.Task<bool> Contains(string key, string scope)
{
    return false;
}

类似于:

[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;
}

感谢您的回答,特别是对一个表面上简单的代码背后发生了什么的深入洞察。有时候很容易忘记在CLR的深处正在发生多少事情。 :) - Sigurd Garshol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接