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

发布于 2024-12-25 04:31:38 字数 768 浏览 3 评论 0原文

我目前正在开发一个基本上使用 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 Tasks 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

赠佳期 2025-01-01 04:31:38

我会尽力回答。 ;)

问题 1:有人可以向我保证隐式方法是个好主意吗?我看到配置等待(假)和遗留/第三方代码中的显式调度引入了很多问题。例如,我如何确保我的“await-ridden”代码始终在 UI 线程上运行?

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

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

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

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. ;)

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?

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.

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?

async/await does not normally use TaskScheduler; they use a "scheduling context" concept. This is actually SynchronizationContext.Current, and falls back to TaskScheduler.Current only if there is no SynchronizationContext. Substituting your own scheduler can therefore be done using SynchronizationContext.SetSynchronizationContext. You can read more about SynchronizationContext 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.

What about changing scheduler midway through a method, just before awaiting an expensive method, and then setting it back again afterward?

If you want to do an expensive operation (presumably on the threadpool), then await the result of TaskEx.Run.

If you want to change the scheduler for other reasons (e.g., concurrency), then await the result of TaskFactory.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 is ConfigureAwait(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:

  • Follow the Task-Based Asynchronous Pattern guidelines.
  • As your code base becomes more asynchronous, it will become more functional in nature (as opposed to traditionally object-oriented). This is normal and should be embraced.
  • As your code base becomes more asynchronous, shared-memory concurrency gradually evolves to message-passing concurrency (i.e., ConcurrentExclusiveSchedulerPair is the new ReaderWriterLock).
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文