使用带有 `Task.Run()` 的 `async` lambda 是多余的吗?

is using an an `async` lambda with `Task.Run()` redundant?

我刚刚遇到一些代码,例如:

var task = Task.Run(async () => { await Foo.StartAsync(); });
task.Wait();

(不,我不知道 Foo.StartAsync() 的内部工作原理)。我最初的反应是摆脱 async/await 并重写为:

var task = Foo.StartAsync();
task.Wait();

这是否正确(同样,对 Foo.StartAsync() 一无所知)。 answer to 似乎表明在某些情况下它可能有意义,但它也表示 "To tell the truth, I haven't seen that many scenarios ..."

大部分是。

像这样使用Task.Run主要供不了解如何执行异步方法的人使用。

但是,还是有区别的。使用 Task.Run 意味着在 ThreadPool 线程上启动异步方法。

当异步方法的同步部分(第一个 await 之前的部分)很重要并且调用者想要确保该方法不阻塞时,这会很有用。

这也可以用于 "get out of" 当前上下文,例如没有 SynchronizationContext.

通常Task.Run预期 用法是在非 CPU 绑定代码上执行-UI 线程。因此,它很少与 async 委托一起使用,但它是可能的(例如,对于同时具有异步和 CPU 绑定部分的代码)。

但是,这是预期的用途。我认为在你的例子中:

var task = Task.Run(async () => { await Foo.StartAsync(); });
task.Wait();

原作者更有可能试图同步阻塞异步代码,并且(滥用)使用 Task.Runavoid deadlocks common in that situation(正如我在我的博客中描述的那样)。

本质上,它看起来像我在 article on brownfield asynchronous code 中描述的 "thread pool hack"。

最好的解决方案是不使用Task.RunWait:

await Foo.StartAsync();

这将导致 async 通过您的代码库增长,这是最好的方法,但现在可能会给您的开发人员带来无法接受的工作量。这大概就是您的前任使用 Task.Run(..).Wait().

的原因

值得注意的是,您的方法必须标记为 async 才能使用 await 关键字。

所编写的代码似乎是 运行 在同步上下文中使用异步代码的变通方法。虽然我不会说你永远不应该这样做,但在几乎所有情况下都最好使用异步方法。

// use this only when running Tasks in a synchronous method
// use async instead whenever possible
var task = Task.Run(async () => await Foo.StartAsync());
task.Wait();

异步方法,如您的 Foo.StartAsync() 示例,应始终 return 一个 Task 对象。这意味着使用 Task.Run() 创建另一个任务在异步方法中通常是多余的。通过使用 await 关键字可以简单地等待由异步方法执行的任务 return。您应该使用 Task.Run() 的唯一原因是当您执行需要在单独线程上执行的 CPU-bound 工作时。通过使用 await 关键字可以简单地等待由异步方法执行的任务 return。您可以在 Microsoft's guide to async programming.

中阅读更多详细信息

在异步方法中,您的代码可以像这样简单:

await Foo.StartAsync();

如果您想在任务 运行ning 期间执行一些其他工作,您可以将函数分配给一个变量,然后 await 结果(任务完成)。

例如:

var task = Foo.StartAsync();
// do some other work before waiting for task to finish
Bar();
Baz();
// now wait for the task to finish executing
await task;

With CPU-bound work that needs to be 运行 on a separate thread, you can use Task.Run(), but you're await 结果而不是使用线程阻塞Task.Wait():

var task = Task.Run(async () => await Foo.StartAsync());
// do some other work before waiting for task to finish
Bar();
Baz();
// now wait for the task to finish executing
await task;