有没有这样写方法的场景:
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 关键字并直接返回 Task 在功能上是等效的。为什么要增加额外的 await
层的额外开销?
当普通方法中的 return
和 async
方法中的 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
对象,这可能在它实际完成之前很久。这意味着第一个版本可能有问题(因为 Foo
处理得太快了),而第二个版本可以正常工作。
如果您不需要 async
(即,您可以直接返回 Task
),则不要使用 async
。
在某些情况下 return await
很有用,例如您有 两个 异步操作要做:
var intermediate = await FirstAsync();
return await SecondAwait(intermediate);
有关 async
性能的更多信息,请参阅 Stephen Toub 关于该主题的 MSDN article 和 video。
更新:我编写了一个更详细的 blog post。
await
在第二种情况下有用?为什么不做 return SecondAwait(intermediate);
?
return SecondAwait(intermediate);
是否也能实现目标?我认为 return await
在这里也是多余的......
await
,你也必须在第二行使用它。
var intermediate = First(); return Second(intermediate)
这样的正常调用,以避免并行引入的开销。在这种情况下不需要异步调用,是吗?
SecondAwait
的返回类型是 `string,错误消息是:“CS4016:由于这是一个异步方法,返回表达式必须是 'string' 类型而不是 'Task
您想要这样做的唯一原因是早期代码中是否存在其他 await
,或者您在返回结果之前以某种方式对结果进行了操作。可能发生这种情况的另一种方式是通过更改异常处理方式的 try/catch
。如果您没有这样做,那么您是对的,没有理由增加创建方法 async
的开销。
return await
(而不仅仅是返回子调用的任务)即使早期代码中有其他等待。你能提供解释吗?
async
那么您将如何等待第一个任务?如果您想使用 any 等待,则需要将该方法标记为 async
。如果该方法被标记为 async
并且您在代码的前面有一个 await
,那么您需要 await
第二个异步操作以使其具有正确的类型。如果您刚刚删除了 await
,则它不会编译,因为返回值的类型不正确。由于方法是 async
,因此结果总是包装在任务中。
async
方法中不返回任务,则返回任务的结果,然后将其包装。
Task<Type>
,而 async
指示返回 Type
(编译器本身会变成 Task<Type>
)。
async
只是用于显式连接延续的语法糖。您需要 async
做任何事情,但是在执行任何重要的异步操作时,它显着地 更容易使用。例如,您提供的代码实际上并没有像您希望的那样传播错误,并且在更复杂的情况下正确执行此操作开始变得相当困难。虽然您从不需要 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>
中,然后就可以走了。
Task<>
是不变的。
如果您不使用 return await 您可能会在调试时或在异常日志中打印时破坏您的堆栈跟踪。
当您返回任务时,该方法实现了它的目的,并且它不在调用堆栈中。当您使用 return await
时,您会将其留在调用堆栈中。
例如:
使用 await 时调用堆栈:A 等待 B 的任务 => B 等待 C 的任务
不使用 await 时调用堆栈:A 等待来自 C 的任务,而 B 已返回。
使原本简单的“thunk”方法异步在内存中创建一个异步状态机,而非异步则没有。虽然这通常可以指出人们使用非异步版本,因为它更有效(这是真的),但这也意味着在挂起的情况下,您没有证据表明该方法涉及“返回/继续堆栈”这有时会让人更难理解这个问题。
所以是的,当性能不重要时(通常不是),我将在所有这些 thunk 方法上抛出异步,以便我有异步状态机来帮助我稍后诊断挂起,并帮助确保如果那些thunk 方法会随着时间的推移而发展,它们肯定会返回错误的任务而不是 throw。
这也让我感到困惑,我觉得以前的答案忽略了你的实际问题:
当您可以从内部 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()
,您将返回一个无法编译的任务。正确的方法是返回任务的结果,所以return await
。
var task = DoSomethingAsync();
会给您一个任务,而不是 T
async/await
的事情。据我了解,Task task = DoSomethingAsync()
和 Something something = await DoSomethingAsync()
都有效。第一个为您提供正确的任务,而第二个,由于 await
关键字,在任务完成后为您提供任务的结果。例如,我可以有 Task task = DoSomethingAsync(); Something something = await task;
。
您可能需要 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
如果您尝试显式类型转换,您将获得 CS0030。不要打扰。
顺便说一下,这是 .NET Framework。我完全可以预见到评论说“在 .NET hypothetical_version 中已修复”,我还没有测试过。 :)
不定期副业成功案例分享
foo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose());
Dispose()
返回void
。您需要类似return foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; });
的内容。但是我不知道当您可以使用第二个选项时为什么要这样做。{ var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; }
。用例非常简单:如果您使用 .NET 4.0(像大多数人一样),您仍然可以以这种方式编写异步代码,这将很好地从 4.5 应用程序调用。Task
完成后才处理Foo
,我不喜欢这样做,因为它不必要地引入了并发性。