运行异步lambda表达式时,使用Task.Run(func)还是new Func<Task>(func)()?

6

我希望能够收集/运行任务,然后对它们执行Task.WhenAll

var tasks = new List<Task>();
foreach (var thing in things) {
  tasks.Add(Task.Run(async () => {
       // async stuff using thing
  }));
}

var stuffs = await Task.WhenAll(tasks);

在这里使用Task.Run是否正确,还是应该做些什么呢?

tasks.Add(new Func<Task>(async () => {async stuff})());

或者完全是其他什么东西?

things 是什么类型? - Ryan Searle
2
tasks.Add(Task.Factory.StartNew(() => {//做一些事情}); await Task.WhenAll(tasks);这将启动所述任务并将其添加到列表中。然后,当所有任务都已添加时,它将停留在等待行上,直到所有任务完成。 - Zexks Marquise
@ZexksMarquise StartNew是危险的,特别是在这里,他的//do stuff包含了await关键字。StartNew不能处理声明为async的匿名函数。 - Scott Chamberlain
@ZexksMarquise 好的,那如果不传递 TaskScheduler 参数呢?当你没有指定时,它会使用 TaskScheduler.FromCurrentSynchronizationContext,这可能会使你从 StartNew 创建的任务在 UI 线程上运行,而不是在后台线程上运行,如果你正在从已经在后台的任务中执行此操作。我链接的文章描述了这个问题。 - Scott Chamberlain
@ZexksMarquise,我不是在争论StartNew和不StartNew的问题,而是在讨论StartNew和Task.Run的区别。如果你这样说:tasks.Add(Task.Run(() => {//Do stuff}); await Task.WhenAll(tasks);,我就不会有任何意见了。 - Scott Chamberlain
显示剩余4条评论
2个回答

9

通常取决于您异步工作的性质。如果您做的是这样的事情:

async () => {
   // some heavy CPU work here
   // then some IO
}

最好使用Task.Run,否则“繁重的CPU工作”将在您当前的线程上运行,您将获得很少的并行化。

然而,这种情况相对较少,因为有其他工具可以用于重型CPU工作的并行化。如果您有类似以下代码:

async () => {
   // some async IO, like database call or web request
   // some processing
   // some more async IO
}

那么你就不需要使用 Task.Run 了。你可以使用你的第二个方法:

Func<Task> t = async () => {
     // some stuff
};
tasks.Add(t());

如果您正在UI线程(在WPF \ WinForms中)上执行此操作-请确保在异步委托内使用ConfigureAwait(false)以防止将控制权返回给UI线程。


明白了,谢谢!我之前不是很确定它们的区别,但你解释得非常清楚。 - Jamie

1

除了Evk's response的回答,还需要注意并行性的潜在差异以及是否需要考虑线程安全。

如果以下所有条件都为真,则“func”不需要考虑线程安全:

  • 您使用new Func方法
  • 您不使用ConfigureAwait(false)
  • SynchronizationContext是同步上下文,意味着它保证只有一个线程可以同时执行代码(例如旧版ASP.NET)

否则,您需要考虑“func”的线程安全性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接