异步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)
我试着回答。)
问题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
的结果。
如果由于其他原因(例如并发)想要更改调度程序,则await
是TaskFactory.StartNew
的结果。
在这两种情况下,方法(或委托)都在另一个调度程序上运行,然后方法的其余部分在其常规上下文中恢复。
理想情况下,您希望每个async
方法都存在于单个执行上下文中。如果方法的不同部分需要不同的上下文,那么将它们划分为不同的方法。该规则的唯一例外是ConfigureAwait(false)
,它允许方法在任意上下文上启动,然后在剩余的执行过程中恢复到线程池上下文。ConfigureAwait(false)
应该被视为一种优化(默认情况下,库代码会启用),而不是一种设计理念。
以下是我在"Thread is Dead"演讲中的一些观点,我认为这些观点可能会对你的设计有所帮助:
- 遵循基于任务的异步模式指导原则
- 随着代码库变得更加异步,它在本质上将变得更加实用(与传统的面向对象相反)。这是正常的,应该接受
- 随着代码库变得更加异步,共享内存并发逐渐演变为消息传递并发(即
ConcurrentExclusiveSchedulerPair
是新的ReaderWriterLock
)