计划程序控制订阅的启动时间和通知的发布时间。 它由三个组件组成。 它首先是一个数据结构。 计划要完成的任务时,会根据优先级或其他条件将其放入计划程序进行排队。 它还提供一个执行上下文,该上下文表示任务执行的位置 (例如线程池、当前线程或另一个应用域) 。 最后,它有一个时钟,它通过访问 Now 计划程序) 的 属性为自身提供时间概念 (。 在特定计划程序上计划的任务将仅遵循该时钟所表示的时间。
计划程序还引入了虚拟时间 (由 VirtualScheduler 类型) 表示的概念,它与日常生活中使用的实时不相关。 例如,指定为需要 100 年才能完成的序列可以计划为在短短 5 分钟内以虚拟时间完成。 测试和 调试可观测序列 主题将对此进行介绍。
计划程序类型
Rx 提供的各种计划程序类型都实现 IScheduler 接口。 可以使用计划程序类型的静态属性创建和返回其中每个属性。 ImmediateScheduler 通过访问静态即时属性 (,) 将立即启动指定的操作。 CurrentThreadScheduler 通过访问静态 CurrentThread 属性 (,) 将计划对进行原始调用的线程执行的操作。 操作不会立即执行,而是放置在队列中,并且仅在当前操作完成后执行。 DispatcherScheduler 通过访问静态调度程序属性 (,) 将计划当前调度程序上的操作,这对使用 Rx 的 Silverlight 开发人员有利。 然后,指定的操作将委托给 Silverlight 中的 Dispatcher.BeginInvoke () 方法。 NewThreadScheduler 通过访问静态 NewThread 属性) 计划新线程上的操作来 (,最适合用于计划长时间运行或阻止操作。 TaskPoolScheduler 通过访问静态 TaskPool 属性) 计划特定任务工厂上的操作来 (。 ThreadPoolScheduler 通过访问静态 ThreadPool 属性) 计划线程池上的操作来 (。 两个池计划程序都针对短期运行操作进行了优化。
使用计划程序
你可能已在 Rx 代码中使用了计划程序,但没有明确说明要使用的计划程序类型。 这是因为处理并发的所有可观测运算符都具有多个重载。 如果不使用以计划程序作为参数的重载,Rx 将使用最小并发原则选择默认计划程序。 这意味着会选择引入满足运算符需求的最少并发的计划程序。 例如,对于返回具有有限和少量消息的可观测对象的运算符,Rx 调用 Immediate。 对于返回可能大量或无限数量的消息的运算符,将调用 CurrentThread 。 对于使用计时器的运算符,使用 ThreadPool 。
由于 Rx 使用最小并发计划程序,因此,如果要引入并发以实现性能目的,或者遇到线程相关性问题,则可以选择其他计划程序。  前者的示例是,如果不想阻止特定线程,在这种情况下,应使用 ThreadPool。  后者的一个示例是,当希望计时器在 UI 上运行时,在这种情况下,应使用 Dispatcher。 若要指定特定的计划程序,可以使用那些采用计划器的运算符重载,例如 Timer(TimeSpan.FromSeconds(10), Scheduler.DispatcherScheduler())。
在以下示例中,源可观测序列以疯狂的速度生成值。 Timer 运算符的默认重载会将 OnNext 消息放在 ThreadPool 上。
Observable.Timer(Timespan.FromSeconds(0.01))
          .Subscribe(…);
这会在观察者上快速排队。 我们可以使用 ObserveOn 运算符改进此代码,该运算符允许您指定要用于向观察者发送推送通知的上下文, (OnNext) 。 默认情况下,ObserveOn 运算符可确保在当前线程上尽可能多次调用 OnNext。 可以使用其重载并将 OnNext 输出重定向到其他上下文。 此外,可以使用 SubscribeOn 运算符返回可观测的代理,该代理将操作委托给特定的计划程序。 例如,对于 UI 密集型应用程序,可以使用 SubscribeOn 并将 ThreadPoolScheduler 传递给计划程序来委托对在后台运行的计划程序执行的所有后台操作。 若要接收推送的通知并访问任何 UI 元素,可以将 DispatcherScheduler 的实例传递给 ObserveOn 运算符。
以下示例将在当前调度程序上计划任何 OnNext 通知,以便在 UI 线程上发送推送的任何值。 这对使用 Rx 的 Silverlight 开发人员尤其有利。
Observable.Timer(Timespan.FromSeconds(0.01))
          .ObserveOn(Scheduler.DispatcherScheduler)
          .Subscribe(…);
我们可以在开始的正确位置创建并发,而不是使用 ObservOn 运算符更改可观测序列生成消息的执行上下文。 由于运算符通过提供计划程序参数重载来参数化并发的引入,传递正确的计划程序将导致必须使用 ObserveOn 运算符的位置更少。 例如,可以通过更改源所使用的计划程序来取消阻止观察程序并直接订阅 UI 线程,如以下示例所示。 在此代码中,通过使用采用计划程序的计时器重载并提供 Scheduler.Dispatcher 实例,从此可观察序列中推送的所有值都将源自 UI 线程。
Observable.Timer(Timespan.FromSeconds(0.01), Scheduler.DispatcherScheduler)
          .Subscribe(…);
还应注意,通过使用 ObservOn 运算符,将针对来自原始可观测序列的每条消息安排一个操作。 这可能会更改计时信息,并给系统带来额外的压力。 如果你有一个查询,该查询组成在许多不同的执行上下文上运行的各种可观察序列,并且你在查询中执行筛选,则最好将 ObservOn 放在查询的后面。 这是因为查询可能会筛选出大量消息,而将 ObserveOn 运算符放在查询的前面,将对无论如何筛选掉的消息执行额外的工作。 在查询结束时调用 ObserveOn 运算符对性能的影响最小。
显式指定计划程序类型的另一个优点是可以引入并发以实现性能目的,如以下代码所示。
seq.GroupBy(...)
        .Select(x=>x.ObserveOn(Scheduler.NewThread))
        .Select(x=>expensive(x))  // perform operations that are expensive on resources