【问题标题】:Async CTP - Recommended approach for task scheduling异步 CTP - 推荐的任务调度方法
【发布时间】:2012-02-04 07:51:52
【问题描述】:

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

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

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

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

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

(p.s. 我已经看过http://msmvps.com/blogs/jon_skeet/archive/2010/11/02/configuring-waiting.aspx)

【问题讨论】:

    标签: c# .net task-parallel-library async-ctp .net-4.5


    【解决方案1】:

    我会尝试回答。 ;)

    问题 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 替换您自己的调度程序。您可以在this MSDN article on the subject 中阅读有关SynchronizationContext 的更多信息。

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

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

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

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

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

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

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

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

    【讨论】:

    • 很好的回答斯蒂芬,但要澄清:如果异步方法'A'在内部使用了ConfigureAwait(false),那么如果我等待方法'A',我希望在哪个上下文中?线程池,还是会在等待调用方法“A”之前恢复原始上下文?
    • 斯蒂芬的回答非常中肯。请注意,如果您对旧模型一无所知,您始终可以创建一个可等待的自定义包装器(类似于 ConfigureAwait() 的工作方式)并将其作为扩展方法挂接到 Task/Task 上。例如,如果您的扩展方法名为 ResumeOn(TaskScheduler ts),那么代码可能如下所示:await Foo(...).ResumeOn(ts);然后具有与您自己的代码相同的调度语义,但具有“等待”带来的所有改进的流程/执行优势。
    • @Lawrence:异步方法的每个“层”都会向下传递其上下文,但不会向上传递。所以如果A调用ConfigureAwait(false),那么它将在线程池上运行完毕。然后,当B 调用await A() 时,B 将在await 之后在它自己的 原始上下文中恢复。 A 在线程池上完成这一事实对 B 的其余部分没有影响。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-20
    • 2011-05-19
    • 1970-01-01
    • 1970-01-01
    • 2013-01-07
    • 1970-01-01
    • 2011-11-07
    相关资源
    最近更新 更多