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

问题描述 投票:200回答:7

有没有像这样编写方法的场景:

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();
}

会有意义吗?

当你可以直接从内部return await调用中返回Task<T>时,为什么要使用DoAnotherThingAsync()构造?

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

c# .net .net-4.5 async-await
7个回答
160
投票

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

考虑这两个版本的方法:

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

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

一旦Dispose()方法返回,第一种方法将Foo DoAnotherThingAsync()对象,这可能早在它实际完成之前很久。这意味着第一个版本可能是错误的(因为Foo处理得太快),而第二个版本可以正常工作。


80
投票

如果你不需要async(即,你可以直接返回Task),那么不要使用async

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

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

有关async表现的更多信息,请参阅Stephen Toub的MSDN articlevideo

更新:我写了一个更详细的blog post


23
投票

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


14
投票

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

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>中的结果然后离开你去。


4
投票

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

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


4
投票

如果您不使用return await,则可能会在调试时或在异常日志中打印时查看堆栈跟踪。

当您返回任务时,该方法实现了它的目的,并且它不在调用堆栈中。当你使用return await时,你将它留在调用堆栈中。

例如:

使用await时调用堆栈:A等待来自B => B的任务等待来自C的任务

不使用await时调用堆栈:A等待来自C的任务,B已返回。


2
投票

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

为什么在可以直接从内部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(),你将返回一个无法编译的Task。正确的方法是返回任务的结果,所以return await

© www.soinside.com 2019 - 2024. All rights reserved.