11

我目前正在开发一个在整个过程中都使用 TAP 的异步应用程序。每个具有Task生成 s 的方法的类也都有一个TaskScheduler注入方法。这使我们能够执行明确的任务调度,据我所知,这不是微软使用 Async CTP 的方式。

我对新方法(隐式调度)的唯一问题是我们以前的理念一直是“我们知道延续将始终指定他们的任务调度程序,所以我们不需要担心我们在什么上下文中完成任务” .

摆脱这一点确实让我们有些担心,因为它在避免细微的线程错误方面工作得非常好,因为对于每一段代码,我们可以看到编码人员已经记住考虑他在哪个线程上。如果他们错过了指定任务调度程序,这是一个错误。

问题 1:谁能向我保证隐式方法是个好主意?我看到 ConfigureAwait(false) 和遗留/第三方代码中的显式调度引入了很多问题。例如,如何确定我的“等待缠身”代码始终在 UI 线程上运行?

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

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

4

1 回答 1

10

我会尝试回答。;)

问题 1:谁能向我保证隐式方法是个好主意?我看到 ConfigureAwait(false) 和遗留/第三方代码中的显式调度引入了很多问题。例如,如何确定我的“等待缠身”代码始终在 UI 线程上运行?

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

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

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

async/await通常不使用TaskScheduler; 他们使用“调度上下文”概念。这实际上是,并且只有在没有时才SynchronizationContext.Current回退到。因此,可以使用 替换您自己的调度程序。您可以在此 MSDN 文章中阅读有关该主题的更多信息。TaskScheduler.CurrentSynchronizationContextSynchronizationContext.SetSynchronizationContextSynchronizationContext

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

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

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

如果您出于其他原因(例如并发)要更改调度程序,awaitTaskFactory.StartNew.

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

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

以下是我的“线程已死”演讲中的一些观点,我认为它们可能对您的设计有所帮助:

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