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

Law*_*eld 11 .net c# task-parallel-library async-ctp .net-4.5

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

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

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

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

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

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

Ste*_*ary 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.您可以SynchronizationContext此MSDN文章中阅读有关该主题的更多信息.

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

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

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

如果您想因其他原因(例如,并发)更改调度程序,则await结果为TaskFactory.StartNew.

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

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

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

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

  • 斯蒂芬的回答非常可靠.请注意,如果您在旧模型上设置了死区,则始终可以创建一个等待的自定义包装器(类似于ConfigureAwait()的工作方式)并将其作为Task/Task <T>上的扩展方法挂钩.例如,如果您的扩展方法名为ResumeOn(TaskScheduler ts),则代码可能如下所示:await Foo(...).ResumeOn(ts); 然后拥有与您自己的代码相同的调度语义,但具有"等待"带来的所有改进的流/执行优势. (2认同)
  • @Lawrence:异步方法的每个"层"都将其上下文传递下来,但不会上传.因此,如果`A`调用`ConfigureAwait(false)`,那么它将在线程池上完成运行.然后,当`B`调用`await A()`时,```将在`await`之后在*自己的*原始上下文中恢复."A"在线程池上完成的事实对"B"的剩余部分没有影响. (2认同)