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

时间:2013-09-30 15:30:36

标签: c# .net .net-4.5 async-await

是否有任何场景,其中编写如下方法:

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图层的额外开销?

7 个答案:

答案 0 :(得分:155)

在正常方法中returnreturn await方法中的async表现不同时有一个偷偷摸摸的情况:与using结合使用时(或者更常见的是,return await 1}}在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处理得太快),而第二个版本可以正常工作。

答案 1 :(得分:78)

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

在某些情况下,return await很有用,例如,如果你有两个异步操作:

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

有关async效果的更多信息,请参阅Stephen Toub关于此主题的MSDN articlevideo

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

答案 2 :(得分:23)

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

答案 3 :(得分:13)

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

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> 1}}。但是,如果您等待结果,它就会变为Foo,其中 可直接分配给IFoo。然后,异步方法只是重新打包Task<IFoo>内的结果,然后离开。

答案 4 :(得分:4)

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

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

答案 5 :(得分:2)

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

  

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

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

从你的代码:

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

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

答案 6 :(得分:2)

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

当您返回任务时,该方法已实现其目的,并且不在调用堆栈中。 当您使用{{1}}时,会将其保留在调用堆栈中。

例如:

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

时使用等待调用堆栈: A正在等待C的任务,B已经返回了。