我刚刚遇到一些代码,例如:
var task = Task.Run(async () => { await Foo.StartAsync(); });
task.Wait();
(不,我不知道
Foo.StartAsync()
的内部运作原理)。我最初的反应是去掉 async
/await
并重写为:
var task = Foo.StartAsync();
task.Wait();
这是否正确(再次强调,对
Foo.StartAsync()
一无所知)。 This回答它有什么区别 - 使用 Task.Run 运行“异步”操作委托...似乎表明在某些情况下它可能有意义,但它也说“说实话,我还没见过那么多场景……”
通常,Task.Run
的
预期用法是在非UI线程上执行CPU密集型代码。因此,它很少与
async
委托一起使用,但这是可能的(例如,对于同时具有异步部分和 CPU 绑定部分的代码)。
但是,这就是预期的用途。我认为在你的例子中:
var task = Task.Run(async () => { await Foo.StartAsync(); });
task.Wait();
原作者更有可能尝试同步阻塞异步代码,并且(ab)使用
Task.Run
来避免这种情况下常见的死锁(正如我在博客中所描述的那样)。
本质上,它看起来像我在有关棕地异步代码的文章中描述的“线程池黑客”。
最好的解决方案是不使用
Task.Run
或Wait
:
await Foo.StartAsync();
这将导致
async
在您的代码库中不断增长,这是最好的方法,但目前可能会给您的开发人员带来不可接受的工作量。这可能就是您的前任使用 Task.Run(..).Wait()
的原因。
大部分是的。
像这样使用
Task.Run
主要是由不了解如何执行异步方法的人使用。
但是,还是有区别的。使用
Task.Run
意味着在 ThreadPool
线程上启动异步方法。
当异步方法的同步部分(第一个等待之前的部分)很大并且调用者希望确保该方法不会阻塞时,这可能很有用。
这也可以用于“退出”当前上下文,例如没有
SynchronizationContext
的地方。
值得注意的是,您的方法必须标记为
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()
示例,应始终返回 Task
对象。这意味着在异步方法中使用 Task.Run()
创建另一个任务通常是多余的。只需使用 await
关键字即可等待异步方法返回的任务。您应该使用 Task.Run()
的唯一原因是当您执行需要在单独线程上执行的 CPU 密集型工作时。只需使用 await
关键字即可等待异步方法返回的任务。您可以在Microsoft 异步编程指南中阅读更深入的详细信息。
在异步方法中,您的代码可以如此简单:
await Foo.StartAsync();
如果您想在任务运行时执行一些其他工作,您可以将函数分配给变量,然后
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;
对于需要在单独线程上运行的 CPU 密集型工作,您可以使用
Task.Run()
,但是您 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;