是否有任何场景,其中编写如下方法:
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
图层的额外开销?
答案 0 :(得分:155)
在正常方法中return
和return 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 article和video。
更新:我写了一篇更详细的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已经返回了。