当你可以返回Task<T>直接吗?

是否存在任何场景,其中写入方法如下:

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

有意义吗?

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

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

60540 次浏览

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

在某些情况下,return await是有用的,比如如果你有两个异步操作要做:

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

有关async性能的更多信息,请参阅Stephen Toub关于该主题的MSDN文章视频

更新:我已经写了一个更详细的博客

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

当正常方法中的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对象,这可能在它实际完成之前很长时间。这意味着第一个版本可能有bug(因为Foo处理得太快了),而第二个版本将正常工作。

另一个你可能需要等待结果的例子是:

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”方法设置为异步会在内存中创建一个异步状态机,而非异步则不会。虽然这通常可以指向人们使用非异步版本,因为它更有效(这是真的),但这也意味着在挂起事件中,你没有证据表明该方法涉及“返回/延续堆栈”,这有时会使人们更难理解挂起。

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

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

当你可以直接从内部DoAnotherThingAsync()调用返回任务时,为什么要使用返回等待构造?

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

从你的代码:

async Task<SomeResult> DoSomethingAsync()
{
using (var foo = new Foo())
{
return await foo.DoAnotherThingAsync();
}
}
不熟悉语法的人(例如我)可能会认为这个方法应该返回Task<SomeResult>,但由于它被标记为async,这意味着它的实际返回类型是SomeResult。 如果你只使用return foo.DoAnotherThingAsync(),你将返回一个任务,它不会编译。正确的方法是返回任务的结果,因此return await.

. 0

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

当你返回任务时,该方法完成了它的目的,它已经退出了调用堆栈。 当你使用return await时,你将它留在调用堆栈中

例如:

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

使用await时调用堆栈:

. A等待C的任务,B已经返回

你可能想要return await的另一个原因:await语法让你避免碰到Task<T>ValueTask<T>类型之间的不匹配。例如,即使SubTask方法返回Task<T>,但它的调用者返回ValueTask<T>,下面的代码也能正常工作。

async Task<T> SubTask()
{
...
}


async ValueTask<T> DoSomething()
{
await UnimportantTask();
return await SubTask();
}

如果你跳过DoSomething()行上的await,你会得到一个编译器错误CS0029:

不能隐式地将类型“System.Threading.Tasks.Task<BlaBla>”转换为“System.Threading.Tasks.ValueTask<BlaBla>”

如果您尝试显式地类型转换它,也会得到CS0030。

顺便说一下,这是。net框架。我完全可以预见到一个评论说“that's fixed in。net __abc0 &;”,我还没有测试它。:)