异步CTP-任务调度的推荐方法

本文关键字:方法 CTP- 任务调度 异步 | 更新日期: 2023-09-27 18:19:34

我目前正在开发一个主要使用TAP的异步应用程序。每个有生成Task的方法的类中也注入了TaskScheduler。这允许我们执行任务的显式调度,据我所知,这不是微软使用Async CTP的方式。

我对新方法(隐式调度)的唯一问题是,我们以前的理念一直是"我们知道延续总是会指定他们的任务调度器,所以我们不需要担心我们在什么上下文中完成任务"。

摆脱这一点确实让我们有点担心,因为它在避免细微的线程错误方面做得非常好,因为对于每一位代码,我们都可以看到程序员都记得考虑他在哪个线程上。如果他们错过了指定任务调度程序,那就是一个错误。

问题1:有人能向我保证隐式方法是个好主意吗?我看到ConfigureAwait(false)和显式调度在遗留/第三方代码中引入了很多问题。例如,我如何确保我的"等待已久"代码始终在UI线程上运行?

问题2:那么,假设我们从代码中删除所有TaskScheduler DI并开始使用隐式调度,那么我们如何设置默认的任务调度程序?在一个方法的中途,就在等待一个昂贵的方法之前,更改调度程序,然后再重新设置它,怎么样?

(附言:我已经读过了http://msmvps.com/blogs/jon_skeet/archive/2010/11/02/configuring-waiting.aspx)

异步CTP-任务调度的推荐方法

我试着回答。)

问题1:有人能向我保证,隐性方法是个好主意吗?我看到ConfigureAwait(false)和显式调度在遗留/第三方代码中引入了很多问题。例如,我如何确保我的"等待已久"代码始终在UI线程上运行?

ConfigureAwait(false)的规则非常简单:如果方法的其余部分可以在线程池上运行,则使用它;如果方法的剩余部分必须在给定上下文(例如UI上下文)中运行,则不使用它。

一般来说,ConfigureAwait(false)应该由库代码使用,而不是由UI层代码使用(包括UI类型层,如MVVM中的ViewModels)。如果该方法部分是后台计算,部分是UI更新,那么它应该分为两种方法。

问题2:那么,假设我们从代码中删除了所有TaskScheduler DI,并开始使用隐式调度,那么我们如何设置默认的任务调度器呢?

async/await通常不使用TaskScheduler;他们使用了"调度上下文"的概念。这实际上是SynchronizationContext.Current,并且只有在没有SynchronizationContext的情况下才回落到TaskScheduler.Current。因此,可以使用SynchronizationContext.SetSynchronizationContext来替换您自己的调度程序。您可以在MSDN关于SynchronizationContext主题的文章中阅读更多内容。

默认的调度上下文应该是你几乎所有时间都需要的,这意味着你不需要搞砸它。我只在进行单元测试或控制台程序/W32服务时更改它。

在一个方法的中途,就在等待一个昂贵的方法之前,更改调度程序,然后再重新设置它,怎么样?

如果你想做一个昂贵的操作(大概是在线程池上),那么await就是TaskEx.Run的结果。

如果由于其他原因(例如并发)想要更改调度程序,则awaitTaskFactory.StartNew的结果。

在这两种情况下,方法(或委托)都在另一个调度程序上运行,然后方法的其余部分在其常规上下文中恢复。

理想情况下,您希望每个async方法都存在于单个执行上下文中。如果方法的不同部分需要不同的上下文,那么将它们划分为不同的方法。该规则的唯一例外是ConfigureAwait(false),它允许方法在任意上下文上启动,然后在剩余的执行过程中恢复到线程池上下文。ConfigureAwait(false)应该被视为一种优化(默认情况下,库代码会启用),而不是一种设计理念。

以下是我在"Thread is Dead"演讲中的一些观点,我认为这些观点可能会对你的设计有所帮助:

  • 遵循基于任务的异步模式指导原则
  • 随着代码库变得更加异步,它在本质上将变得更加实用(与传统的面向对象相反)。这是正常的,应该接受
  • 随着代码库变得更加异步,共享内存并发逐渐演变为消息传递并发(即ConcurrentExclusiveSchedulerPair是新的ReaderWriterLock