Task.Yield、Task.Run和ConfigureAwait(false)有什么区别?

13
据我所知,在方法开头使用Task.Yield将会强制调用者继续执行,如果它没有等待该方法。同时,Task.RunConfigureAwait(false)both会在新的线程池线程上运行一个任务,这也会强制调用者继续执行,如果它没有等待该方法。
我无法理解Task.Yield和在新的线程池线程上运行之间的区别,因为在它返回给调用者后,它将继续执行方法的其余部分,这本质上是相同的事情。 这篇文章建议可以互换使用YieldTask.Factory.StartNew(实际上只是Task.Run的旧版本),这对我来说似乎很令人困惑。

1
Task.Run = Task.Factory.StartNew - hungndv
1
@hungndv Task.RunTask.Factory.StartNew 的一种特例,它使用默认任务调度程序(线程池任务调度程序)执行。而 Task.Factory.StartNew 如果没有指定任务调度程序,则会选择当前任务调度程序,但这可能不是默认的调度程序。例如,它可能是 UI 任务调度程序。Task.Run 总是在线程池中执行。因此,Task.RunTask.Factory.StartNew 的一种特殊情况,不是其新版本。 - Thanasis Ioannidis
2个回答

12

Task.Yield不是Task.Run的替代品,也与Task.ConfigureAwait无关。

  • Task.Yield - 生成一个可等待对象,在检查完成后立即完成。
  • ConfigureAwait(false) - 从一个任务生成一个可等待对象,忽略了捕获的同步上下文。
  • Task.Run - 在ThreadPool线程上执行委托。

Task.YieldConfigureAwait的不同之处在于它本身就是一个可等待对象,而不是另一个可等待对象(即Task)的可配置包装器。另一个区别是Task.Yield确实在捕获的上下文中继续执行。

Task.Run与两者都不同,因为它只需要一个委托并在ThreadPool上运行它,你可以使用或不使用ConfigureAwait(false)

Task.Yield应该用于强制执行异步点,而不是替换Task.Run。当到达异步方法中的await时,它会检查任务(或其他可等待对象)是否已经完成,如果已经完成,则继续以同步方式执行。Task.Yield可以防止这种情况发生,因此它在测试中很有用。

另一个用途是在UI方法中,在这些方法中,您不希望独占单个UI线程,您需要插入异步点,其余操作将在稍后执行。


9
Task.Yield无法解决UI限制问题,因为UI消息队列是有优先级的(继续执行总是优于WM_PAINT)。 - Stephen Cleary
同时,您永远不希望尽可能快地绘制。那样会浪费一个核心的计算能力。 - usr
@StephenCleary:是谁在UI消息队列中发布续集的?只有在某个时刻调用了BeginInvoke才会发生这种情况。 - v.oddou
2
@v.oddou:后续问题应该成为他们自己的问题(SO是一个问答网站,而不是论坛)。简短回答:await将把方法继续发布到当前的SynchronizationContext实例,该实例在内部使用BeginInvoke(或等效)来执行。 - Stephen Cleary

7

Task.Yield会在当前同步上下文或当前的TaskScheduler上继续执行,如果有的话。而Task.Run则不会这样做,它总是使用线程池。

例如,Task.Yield将保持在UI线程上。

避免使用Task.Yield,其语义不够清晰。链接的答案是一种代码异味。


Task.Yield 使用 TaskScheduler.Current 的一个好处是,这是 TPL 团队另一个值得怀疑的设计决策。这个决策不能通过 .NET 4.0 兼容性原因来证明,因为在 .NET 4.0 中没有 Task.Yield - noseratio - open to work
@i3arnon,我并不质疑在存在同步上下文时Task.Yield的行为。但是在没有同步上下文的情况下,我对使用TaskScheduler.CurrentTaskScheduler.Default进行了质疑。我坚信应该使用后者,就像Task.Run一样。 - noseratio - open to work
我真的不会把在某些可配置上下文中恢复默认选择称为安全选择!虽然对于 UI 应用程序来说很方便,但除此之外它非常危险。这是对可变全局状态的隐藏依赖,可能会注入任意线程行为。非常不安全。 - usr
@Noseratio,我也没有谈论SC。await在当前TS上恢复,使用Task.Yield也不应该有任何不同。Task.RunTask.Yield无关,它明确地在不同的TS上执行代码。 - i3arnon
@i3arnon,在缺少同步上下文的情况下,我认为使用TaskScheduler.Current来进行await是一个不好的决定。这是一个非常不直观和微妙的行为。Toub在这里提供了一个很好的例子,展示了它可能出错的情况:http://blogs.msdn.com/b/pfxteam/archive/2012/09/22/new-taskcreationoptions-and-taskcontinuationoptions-in-net-4-5.aspx。 - noseratio - open to work
显示剩余2条评论

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