首页 文章

在C#中“返回等待”的目的是什么?

提问于
浏览
171

是否 any 场景写作方法如下:

public async Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return await DoAnotherThingAsync();
}

而不是这个:

public Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return DoAnotherThingAsync();
}

会有意义吗?

Why use return await construct when you can directly return Task<T> from the inner DoAnotherThingAsync() invocation?

我在很多地方看到 return await 的代码,我想我应该错过一些东西 . 但据我了解,在这种情况下不使用async / await关键字并直接返回Task将在功能上等效 . 为什么要增加额外的 await 图层的额外开销?

6 回答

  • 22

    当正常方法中的 returnasync 方法中的 return await 表现不同时有一个偷偷摸摸的情况:当与 using (或更一般地, try 块中的任何 return await )组合时 .

    考虑这两个版本的方法:

    Task<SomeResult> DoSomethingAsync()
    {
        using (var foo = new Foo())
        {
            return foo.DoAnotherThingAsync();
        }
    }
    
    async Task<SomeResult> DoSomethingAsync()
    {
        using (var foo = new Foo())
        {
            return await foo.DoAnotherThingAsync();
        }
    }
    

    一旦 DoAnotherThingAsync() 方法返回,第一个方法就会 Dispose() Foo 对象,这可能早在实际完成之前 . 这意味着第一个版本可能是错误的(因为 Foo 过早地处理),而第二个版本可以正常工作 .

  • 65

    如果您不需要 async (即,您可以直接返回 Task ),则不要使用 async .

    在某些情况下 return await 很有用,就像你有两个异步操作一样:

    var intermediate = await FirstAsync();
    return await SecondAwait(intermediate);
    

    有关 async 表现的更多信息,请参阅Stephen Toub关于该主题的MSDN articlevideo .

    Update: 我写了一篇更详细的blog post .

  • 2

    您想要这样做的唯一原因是,如果在早期代码中有其他 await ,或者如果您在返回之前以某种方式操纵结果 . 另一种可能发生的方式是通过改变异常处理方式的 try/catch . 如果你不是正确的话,没有理由增加制作方法 async 的开销 .

  • 134

    您可能需要等待结果的另一个案例是:

    async Task<IFoo> GetIFooAsync()
    {
        return await GetFooAsync();
    }
    
    async Task<Foo> GetFooAsync()
    {
        var foo = await CreateFooAsync();
        await foo.InitializeAsync();
        return foo;
    }
    

    在这种情况下, GetIFooAsync() 必须等待 GetFooAsync 的结果,因为 T 的类型在两种方法之间是不同的,并且 Task<Foo> 不能直接分配给 Task<IFoo> . 但是如果你等待结果,它就会变成 Foo ,它可以直接分配给 IFoo . 然后async方法只是重新打包 Task<IFoo> 中的结果然后离开你 .

  • 12

    使异常简单的“thunk”方法异步在内存中创建异步状态机,而非异步状态机则不在内存中 . 虽然这通常指向人们使用非异步版本,因为它更有效(这是真的)它也意味着在挂起的情况下,你没有证据表明该方法涉及“返回/继续堆栈”这有时会让人更难理解这个问题 .

    所以是的,当perf不是关键的(通常不是)时,我会在所有这些thunk方法上抛出异步,这样我就可以使用异步状态机帮助我以后诊断挂起,并帮助确保如果那些thunk方法随着时间的推移而发展,他们肯定会返回故障任务而不是抛出 .

  • 3

    这也让我感到困惑,我觉得以前的答案忽略了你的实际问题:

    为什么在可以直接从内部DoAnotherThingAsync()调用返回Task时使用return await构造?

    好吧,有时你真的想要一个 Task<SomeType> ,但大多数时候你真的想要一个 SomeType 的实例,也就是来自任务的结果 .

    从您的代码:

    async Task<SomeResult> DoSomethingAsync()
    {
        using (var foo = new Foo())
        {
            return await foo.DoAnotherThingAsync();
        }
    }
    

    不熟悉语法的人(例如我)可能认为此方法应该返回 Task<SomeResult> ,但由于它标有 async ,这意味着它的实际返回类型是 SomeResult . 如果你只是使用 return foo.DoAnotherThingAsync() ,那么你就可以编译了.1448395_ . 正确的方法是返回任务的结果,所以 return await .

相关问题