异步 CTP - 推荐的任务调度方法
我目前正在开发一个基本上使用 TAP 的异步应用程序。每个具有生成 Task
方法的类也会注入一个 TaskScheduler
。这使我们能够执行显式的任务调度,据我了解,这不是 Microsoft 异步 CTP 的方式。
我对新方法(隐式调度)的唯一问题是,我们之前的理念始终是“我们知道延续将始终指定其任务调度程序,因此我们无需担心我们在什么上下文中完成任务” 。
远离这一点确实让我们有点担心,因为它在避免微妙的线程错误方面效果非常好,因为对于每一位代码,我们都可以看到编码器记得考虑他所在的线程。如果他们错过了指定任务调度程序,那就是一个错误。
问题 1:有人可以向我保证隐式方法是个好主意吗?我看到配置等待(假)和遗留/第三方代码中的显式调度引入了很多问题。例如,我如何确保“充满等待”的代码始终在 UI 线程上运行?
问题 2:那么,假设我们从代码中删除所有 TaskScheduler
DI 并开始使用隐式调度,那么我们如何设置默认任务调度程序?在等待一个昂贵的方法之前,在一个方法的中途更改调度程序,然后再将其设置回来怎么样?
(ps我已经阅读http://msmvps.com/blogs/jon_skeet/存档/2010/11/02/configuring-waiting.aspx)
I'm currently working on a largely asynchronous application which uses TAP throughout. Every class which has methods for spawning Task
s also has a TaskScheduler
injected into it. This allows us to perform explicit scheduling of tasks, which as I understand, is not the way Microsoft are going with the Async CTP.
The only issue I have with the new approach (implicit scheduling) is that our previous philosophy has always been "we know the continuation will always specify their task scheduler, so we don't need to worry about what context we complete the task on".
Moving away from that does worry us slightly just because it has worked extremely well in terms of avoiding subtle threading errors, because for every bit of code we can see that the coder has remembered to consider what thread he's on. If they missed specifying the task scheduler, it's a bug.
Question 1: Can anyone reassure me that the implicit approach is a good idea? I see so many issues being introduced by ConfigureAwait(false) and explicit scheduling in legacy/third party code. How can I be sure my 'await-ridden' code is always running on the UI thread, for example?
Question 2: So, assuming we remove all TaskScheduler
DI from our code and begin to use implicit scheduling, how do we then set the default task scheduler? What about changing scheduler midway through a method, just before awaiting an expensive method, and then setting it back again afterward?
(p.s. I have already read http://msmvps.com/blogs/jon_skeet/archive/2010/11/02/configuring-waiting.aspx)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我会尽力回答。 ;)
ConfigureAwait(false)
的规则非常简单:如果方法的其余部分可以在线程池上运行,则使用它;如果方法的其余部分必须在给定的线程池中运行,则不要使用它上下文(例如,UI 上下文)。一般来说,
ConfigureAwait(false)
应该由库代码使用,而不是由 UI 层代码(包括 UI 类型层,例如 MVVM 中的 ViewModel)使用。如果该方法是部分后台计算和部分UI更新,那么它应该分为两个方法。async
/await
通常不使用TaskScheduler
;他们使用“调度上下文”概念。这实际上是SynchronizationContext.Current
,并且仅当没有SynchronizationContext
时才会回退到TaskScheduler.Current
。因此,可以使用SynchronizationContext.SetSynchronizationContext
替换您自己的调度程序。您可以在这篇 MSDN 文章中阅读有关SynchronizationContext
的更多信息主题。默认的调度上下文应该是您几乎所有时间都需要的,这意味着您不需要弄乱它。我仅在进行单元测试或控制台程序/Win32 服务时更改它。
如果您想要执行昂贵的操作(大概是在线程池上),那么
等待
TaskEx.Run
的结果。如果您出于其他原因(例如并发)想要更改调度程序,则
等待
TaskFactory.StartNew
的结果。在这两种情况下,方法(或委托)在另一个调度程序上运行,然后方法的其余部分在其常规上下文中恢复。
理想情况下,您希望每个异步方法都存在于单个执行上下文中。如果方法的不同部分需要不同的上下文,则将它们分成不同的方法。此规则的唯一例外是
ConfigureAwait(false)
,它允许方法在任意上下文上启动,然后恢复到线程池上下文以执行剩余的执行。ConfigureAwait(false)
应被视为一种优化(库代码默认启用),而不是一种设计理念。以下是我的“线程已死”演讲中的一些要点,我认为这些要点可能会对您的设计有所帮助:
ConcurrentExclusiveSchedulerPair
是新的ReaderWriterLock
)。I'll take a shot at answering. ;)
The rules for
ConfigureAwait(false)
are pretty simple: use it if the rest of your method can be run on the threadpool, and don't use it if the rest of your method must run in a given context (e.g., UI context).Generally speaking,
ConfigureAwait(false)
should be used by library code, and not by UI-layer code (including UI-type layers such as ViewModels in MVVM). If the method is partially-background-computation and partially-UI-updates, then it should be split into two methods.async
/await
does not normally useTaskScheduler
; they use a "scheduling context" concept. This is actuallySynchronizationContext.Current
, and falls back toTaskScheduler.Current
only if there is noSynchronizationContext
. Substituting your own scheduler can therefore be done usingSynchronizationContext.SetSynchronizationContext
. You can read more aboutSynchronizationContext
in this MSDN article on the subject.The default scheduling context should be what you need almost all of the time, which means you don't need to mess with it. I only change it when doing unit tests, or for Console programs / Win32 services.
If you want to do an expensive operation (presumably on the threadpool), then
await
the result ofTaskEx.Run
.If you want to change the scheduler for other reasons (e.g., concurrency), then
await
the result ofTaskFactory.StartNew
.In both of these cases, the method (or delegate) is run on the other scheduler, and then the rest of the method resumes in its regular context.
Ideally, you want each
async
method to exist within a single execution context. If there are different parts of the method that need different contexts, then split them up into different methods. The only exception to this rule isConfigureAwait(false)
, which allows a method to start on an arbitrary context and then revert to the threadpool context for the remainder of its execution.ConfigureAwait(false)
should be considered an optimization (that's on by default for library code), not as a design philosophy.Here's some points from my "Thread is Dead" talk that I think may help you with your design:
ConcurrentExclusiveSchedulerPair
is the newReaderWriterLock
).