System.Reactive基础——向上下文发送通知
问题
System.Reactive最大限度地实现了无差别对待不同的线程。无论当前是什么线程,它都会在其中发送通知。比如OnNext.每个OnNext通知都是按顺序出现的,但未必总在相同的线程中
假设要在一个特定的上限为中发送通知,比如UI元素只应在包含他们的UI线程中操作,如果有通知从线程池传来,而你正在更新响应瓷通知的UI,则需要移步至UI线程
解决方案
System.Reactiv提供的ObServeOn运算符,可以把通知转移到其他调度器中。
观察下面的代码,其中的Interval运算符每过一秒发送一次OnNext通知:
private void Button_Click(object sender,RoutedEventArgs e)
{
Trace.WriteLine("UI thread is {Environment.CurrentManagedThreadId}");
Observable.Interval(TimeSpan.FromSeconds(1)).Subscribe(
x => Trace.WriteLine("Interval{x} on thread {Environment.CurrentManagedThreadId}"));
}
以我的计算机为例,输出如下:
Interval0 on thread 5
Interval1 on thread 5
Interval2 on thread 4
Interval3 on thread 5
Interval4 on thread 4
Interval5 on thread 4
Interval6 on thread 4
Interval7 on thread 4
Interval8 on thread 4
Interval9 on thread 4
因为Interval基于计时器(没有特定的线程),所以通知来自线程池线程,而非UI线程,如果需要更新UI元素,那么可以通过ObserveOn传递这些通知,并传递一个表示UI线程的同步上下文:
private void button4_Click(object sender, EventArgs e)
{
SynchronizationContext uiContext = SynchronizationContext.Current;
Trace.WriteLine("UI thread is {Environment.CurrentManagedThreadId}");
Observable.Interval(TimeSpan.FromSeconds(1)).ObserveOn(uiContext).Subscribe(
x => Trace.WriteLine("Interval {x} on thread {Environment.CurrentManagedThreadId}"));
ObserveOn的另一个常见用法是在必要时离开UI线程。假设在某种情况下,每当移动鼠标时,便需要进行CPU密集型计算。在默认情况下,所有的鼠标移动都来自UI线程,可以通过ObserveOn把这些通知转移到线程池线程,然后进行计算,最后把结果通过通知折返到UI线程:
SynchronizationContext uiContext = SynchronizationContext.Current;
Trace.WriteLine("UI thread is {Environment.CurrentManagedThreadId}");
Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
handler => (s, a) => handler(s, a),
handler => MouseMove += handler,
handler => MouseMove -= handler
).Select(evt => evt.EventArgs.GetPosition(this)).ObserveOn(Scheduler.Default).Select(position =>
{
Thread.Sleep(100);
var result = position.X + position.Y;
var thread = Environment.CurrentManagedThreadId;
Trace.WriteLine("Calculated result {result} on thread {thread}");
return result;
}).ObserverOn(uiContext).Subscribe(x => Trace.WriteLine($"Result {x} on thread {Environment.CurrentManagedThreadId}"));
讨论
ObserveOn会把通知移至System.Reactive调度器,本节所述的只是默认的线程池调度器和创建UI调度器的一种方式而已。ObserveOn运算符最常见的用法是进出于UI线程,但电镀漆在其他场景中也很有用。
ObserveOn控制可观察通知的上下文,请勿将它和SubscribeOn混淆,后者控制的是负责添加和移除事件处理程序的代码所在的上下文。