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

时间:2012-01-06 15:36:44

标签: c# .net task-parallel-library async-ctp .net-4.5

我目前正在开发一个在很大程度上使用TAP的异步应用程序。每个具有产生Task的方法的类也都注入了TaskScheduler。这允许我们执行任务的显式调度,据我所知,这不是Microsoft使用Async CTP的方式。

我对新方法(隐式调度)的唯一问题是我们以前的哲学一直是“我们知道延续将始终指定他们的任务调度器,所以我们不需要担心我们完成的上下文任务“。

远离它确实让我们感到担忧,因为它在避免细微的线程错误方面效果非常好,因为对于每一段代码我们都可以看到编码器已经记住要考虑他所在的线程。如果他们错过了指定任务调度程序,那就是一个错误。

问题1:任何人都可以向我保证隐含的方法是个好主意吗?我看到ConfigureAwait引入了很多问题(false)以及传统/第三方代码中的显式调度。例如,我怎样才能确定我的'await-ridden'代码始终在UI线程上运行?

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

(p.s。我已阅读http://msmvps.com/blogs/jon_skeet/archive/2010/11/02/configuring-waiting.aspx

1 个答案:

答案 0 :(得分:10)

我会小心翼翼地回答。 ;)

  

问题1:任何人都可以向我保证隐含的方法是个好主意吗?我看到ConfigureAwait引入了很多问题(false)以及传统/第三方代码中的显式调度。例如,我怎样才能确定我的'await-ridden'代码始终在UI线程上运行?

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

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

  

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

async / await通常不会使用TaskScheduler;他们使用“调度上下文”概念。这实际上是SynchronizationContext.Current,只有在没有TaskScheduler.Current时才会回退到SynchronizationContext。因此,可以使用SynchronizationContext.SetSynchronizationContext替换您自己的调度程序。您可以在this MSDN article on the subject中详细了解SynchronizationContext

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

  

如果在等待昂贵的方法之前在方法中途更改调度程序,然后再将其重新设置呢?

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

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

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

  

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

以下是我的“Thread is Dead”演讲中的一些观点,我认为可以帮助您完成设计:

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