有没有像这样编写方法的场景:
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
层的额外开销?
有一个偷偷摸摸的情况,正常方法中的return
和return 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
处理得太快),而第二个版本可以正常工作。
如果你不需要async
(即,你可以直接返回Task
),那么不要使用async
。
在某些情况下,return await
很有用,就像你有两个异步操作一样:
var intermediate = await FirstAsync();
return await SecondAwait(intermediate);
有关async
表现的更多信息,请参阅Stephen Toub的MSDN article和video。
更新:我写了一个更详细的blog post。
您想要这样做的唯一原因是,如果在早期代码中有其他await
,或者如果您在返回之前以某种方式操纵结果。另一种可能发生的方式是通过改变异常处理方式的try/catch
。如果你没有做任何事情,那么你是对的,没有理由增加制作方法async
的开销。
您可能需要等待结果的另一个案例是:
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>
中的结果然后离开你去。
使异常简单的“thunk”方法异步在内存中创建异步状态机,而非异步状态机则不在内存中。虽然这通常指向人们使用非异步版本,因为它更有效(这是真的)它也意味着在挂起的情况下,你没有证据表明该方法涉及“返回/继续堆栈”这有时会让人更难理解这个问题。
所以是的,当perf不是关键的(通常不是)时,我会在所有这些thunk方法上抛出异步,这样我就可以使用异步状态机帮助我以后诊断挂起,并帮助确保如果那些thunk方法随着时间的推移而发展,他们肯定会返回故障任务而不是抛出。
如果您不使用return await,则可能会在调试时或在异常日志中打印时查看堆栈跟踪。
当您返回任务时,该方法实现了它的目的,并且它不在调用堆栈中。当你使用return await
时,你将它留在调用堆栈中。
例如:
使用await时调用堆栈:A等待来自B => B的任务等待来自C的任务
不使用await时调用堆栈:A等待来自C的任务,B已返回。
这也让我感到困惑,我觉得以前的答案忽略了你的实际问题:
为什么在可以直接从内部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
。