[RxUI] 14 – 调度
调度是编写任何使用Reactive Extension程序的核心部分,因为所有操作符都可以被延迟(即在其它线程或UI线程上运行)。调度器允许程序控制上下文代码在其中运行,并很重要的一点是在其它线程上运行代码的库必须能够识别调度器。ReactiveUI提供了两个程序级的调度器,应该用它们替代其它调度器(例如内置的Rx调度器):
- RxApp.MainThreadScheduler — 此调度器在UI线程上执行。在基于XAML的平台上,等效于Dispatcher.BeginInvoke。
- RxApp.TaskpoolScheduler — 此调度程序通过TPL任务池执行代码。等效于Task.Run。
要使用这两个内置调度器,需要在Observable链中使用ObserveOn
操作符:
this.WhenAnyValue(x => x.MyImportantProperty).ObserveOn(RxApp.MainThreadScheduler).Subscribe(x => ...);
要控制ReactiveControl
在哪里运行订阅,可以传入一个调度器。默认情况下,会使用当前线程的调度器,因此,如果从UI线程初始化,就将使用UI线程运行。
MyCommand = ReactiveCommand.Create<Unit, string>(_ => ...do stuff..., outputScheduler: RxApp.
MainThreadScheduler);
可以通过传入调度器来控制ObservableAsPropertyHelper
在何处触发INotifyPropertyChanged
事件。默认情况下,会使用当前线程的调度器,因此,如果从UI线程初始化,将使用UI线程运行。
public class MyVm : ReactiveObject
{
private readonly ObservableAsPropertyHelper<bool> _isRunning;
public MyVm()
{
MyCommand = ReactiveCommand.Create<Unit, string>(_ => ...do stuff..., outputScheduler: RxApp.MainThreadScheduler);
_isRunning = MyCommand.IsExecuting.ToProperty(this, nameof(IsRunning), scheduler: RxApp.MainThreadScheduler);
}
public ReactiveCommand<Unit, string> MyCommand { get; }
public bool IsRunning => _isRunning.Value;
}
何时关心调度
应该尝试尝试删除所有的并发源,而不是通过RxAPP进行调度。这并非总是可行,但是通过new Thread()
或Task.Run
创建的线程无法在单元测试中进行控制。解决这些问题的最直接方法是将它们替换为Observable.Start
:
之前
var result = await Task.Run(() => {
int number = ThisCalculationTakesALongTime();
return number;
});
Dispatcher.BeginInvoke(new Action(() => DoAThing()));
现在
var result = await Observable.Start(() => {
int number = ThisCalculationTakesALongTime();
return number;
}, RxApp.TaskpoolScheduler);
RxApp.MainThreadScheduler.Schedule(() => DoAThing());
如果创建共享组件,则还应考虑允许将调度器指定为可选的构造函数参数。
测试调度器
在默认情况下,在单元测试运行器中,MainThreadScheduler
立即运行代码,而不是在(不存在的)UI线程上运行代码。默认情况下,TaskpoolScheduler
保持不变。在交叉调度器下运行的最佳方法是使用With
方法,该方法最常与TestScheduler
一起使用。这将两个调度器替换为指定的调度器:
new TestScheduler().With(sheduler =>
{
// Code run in this block will have both RxApp.MainThreadScheduler
// and RxApp.TaskpoolScheduler assigned to the new TestScheduler.
});