Net中的反应式编程
Net中的反应式编程(Reactive Programming)
系列主题:基于消息的软件架构模型演变
一、反应式编程(Reactive Programming)
1、什么是反应式编程:反应式编程(Reactive programming)简称Rx,他是一个使用LINQ风格编写基于观察者模式的异步编程模型。简单点说Rx = Observables + LINQ + Schedulers。
2、为什么会产生这种风格的编程模型?我在本系列文章开始的时候说过一个使用事件的例子:
|
1
2
3
4
5
6
7
8
9
|
var watch = new FileSystemWatcher(); watch.Created += (s, e) => { var fileType = Path.GetExtension(e.FullPath); if (fileType.ToLower() == "jpg") { //do some thing } }; |
这个代码定义了一个FileSystemWatcher,然后在Watcher事件上注册了一个匿名函数。事件的使用是一种命令式代码风格,有没有办法写出声明性更强的代码风格?我们知道使用高阶函数可以让代码更具声明性,整个LINQ扩展就是一个高阶函数库,常见的LINQ风格代码如下:
|
1
2
3
4
|
var list = Enumerable.Range(1, 10) .Where(x => x > 8) .Select(x => x.ToString()) .First(); |
能否使用这样的风格来编写事件呢?
3、事件流
LINQ是对IEnumerable<T>的一系列扩展方法,我们可以简单的将IEnumerable<T>认为是一个集合。当我们将事件放在一个时间范围内,事件也变成了集合。我们可以将这个事件集合理解为事件流。

事件流的出现给了我们一个能够对事件进行LINQ操作的灵感。
二、反应式编程中的两个重要类型
事件模型从本质上来说是观察者模式,所以IObservable<T>和IObserver<T>也是该模型的重头戏。让我们来看看这两个接口的定义:
|
1
2
3
4
5
|
public interface IObservable<out T>{ //Notifies the provider that an observer is to receive notifications. IDisposable Subscribe(IObserver<T> observer);} |
|
1
2
3
4
5
6
7
8
9
10
11
|
public interface IObserver<in T>{ //Notifies the observer that the provider has finished sending push-based notifications. void OnCompleted(); //Notifies the observer that the provider has experienced an error condition. void OnError(Exception error); //Provides the observer with new data. void OnNext(T value);} |
这两个名称准确的反应出了它两的职责:IObservable<T>-可观察的事物,IObserver<T>-观察者。
IObservable<T>只有一个方法Subscribe(IObserver<T> observer),此方法用来对事件流注册一个观察者。
IObserver<T>有三个回调方法。当事件流中有新的事件产生的时候会回调OnNext(T value),观察者会得到事件中的数据。OnCompleted()和OnError(Exception error)则分别用来通知观察者事件流已结束,事件流发生错误。
显然事件流是可观察的事物,我们用Rx改写上面的例子:
|
1
2
3
4
5
6
|
Observable.FromEventPattern<FileSystemEventArgs>(watch, "Created") .Where(e => Path.GetExtension(e.EventArgs.FullPath).ToLower() == "jpg") .Subscribe(e => { //do some thing }); |
注:在.net下使用Rx编程需要安装以下Nuget组件:
- Rx-Core
- Rx-Interfaces
- Rx-Linq
三、UI编程中使用Rx
Rx模型不但使得代码更加具有声明性,而且使得UI编程更加简单。
1、UI编程中的第一段Rx代码
为了简单的展示如何在UI编程中使用Rx,我们以Winform中的Button为例,看看事件模型和Rx有何不同。
|
1
2
3
4
5
6
7
8
9
|
private void BindFirstGroupButtons() { btnFirstEventMode.Click += btnFirstEventMode_Click; } void btnFirstEventMode_Click(object sender, EventArgs e) { MessageBox.Show("hello world"); } |
添加了一个Button,点击Button的时候弹出一个对话框。使用Rx做同样的实现:
|
1
2
3
4
|
//得到了Button的Click事件流。var clickedStream = Observable.FromEventPattern<EventArgs>(btnFirstReactiveMode, "Click");//在事件流上注册了一个观察者。 clickedStream.Subscribe(e => MessageBox.Show("Hello world")); |
有朋友指出字符串“Click”非常让人不爽,这确实是个问题。由于Click是一个event类型,无法用表达式树获取其名称,最终我想到使用扩展方法来实现:
|
1
2
3
4
5
6
7
8
9
|
public static IObservable<EventPattern<EventArgs>> FromClickEventPattern(this Button button) { return Observable.FromEventPattern<EventArgs>(button, "Click"); } public static IObservable<EventPattern<EventArgs>> FromDoubleClickEventPattern(this Button button) { return Observable.FromEventPattern<EventArgs>(button, "DoubleClick"); } |
我们平时常用的事件类型也就那么几个,可以暂时通过这种方案来实现,该方案算不上完美,但是比起直接使用字符串又能优雅不少。
|
1
2
|
btnFirstReactiveMode.FromClickEventPattern() .Subscribe(e => MessageBox.Show("hello world")); |
2、UI编程中存在一个很常见的场景:当一个事件的注册者阻塞了线程时,整个界面都处于假死状态。.net中的异步模型也从APM,EAP,TPL不断演化直至async/await模型的出现才使得异步编程更加简单易用。我们来看看界面假死的代码:
|
1
2
3
4
5
6
|
void btnSecondEventMode_Click(object sender, EventArgs e) { btnSecondEventMode.BackColor = Color.Coral; Thread.Sleep(5000); MessageBox.Show("hello world"); } |
Thread.Sleep(5000);模拟了一个长时间的操作,当你点下Button时整个界面处于假死状态并且此时的程序无法响应其他的界面事件。传统的解决方案是使用多线程来解决假死:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
void BtnSecondEventAsyncModel_Click(object sender, EventArgs e){ Action action = () => { Task.Run(() => { BtnSecondEventAsyncModel.BackColor = Color.Coral; Thread.Sleep(5000); MessageBox.Show("hello world"); }); }; BeginInvoke(action);} |
这个代码的复杂点在于:普通的多线程无法对UI进行操作,在Winform中需要用Control.BeginInvoke(Action action)经过包装后,多线程中的UI操作才能正确执行,WPF则要使用Dispatcher.BeginInvoke(Action action)包装。
在Rx中选择合适的Scheduler类型就可以轻松解决多线程更新UI的问题。
|
1
2
3
4
5
6
7
8
|
btnSecondReactiveMode.FromClickEventPattern() .ObserveOn(new NewThreadScheduler()) .Subscribe(e => { btnSecondReactiveMode.BackColor = Color.Coral; Thread.Sleep(5000); MessageBox.Show("hello world"); }); |
一句ObserveOn(new NewThreadScheduler())成功让后面的观察者跑在了新线程中,并且避免了多线程更新UI的问题,使用者无需再被这种问题所困扰。——抱歉,这一段描述有问题,简单的使用NewThreadScheduler并不能保证异步更新UI。待我周末了详细补充一下Rx中的异步能力。
注:使用Scheduler需要从Nuget中安装Rx-PlatformServices。
常用的Scheduler有:
- DefaultScheduler:默认的Scheduler,会将观察者添加在调用队列中
- ImmediateScheduler:立即执行
- TaskPoolScheduler:用来执行短时间的任务
- ...
3、再来一个例子,让我们感受一下Rx的魅力

界面上有两个Button分别为+和-操作,点击+按钮则+1,点击-按钮则-1,最终的结果显示在一个Label中。
这样的一个需求使用经典事件模型只需要维护一个内部变量,两个按钮的Click事件分别对变量做加1或减1的操作即可。
Rx作为一种函数式编程模型讲求immutable-不可变性,即不使用变量来维护内部状态。
|
1
2
3
4
5
6
7
8
|
var increasedEventStream = btnIncreasement.FromClickEventPattern() .Select(_ => 1);var decreasedEventStream = btnDecrement.FromClickEventPattern() .Select(_ => -1);increasedEventStream.Merge(decreasedEventStream) .Scan(0, (result, s) => result + s) .Subscribe(x => lblResult.Text = x.ToString()); |
这个例子使用了IObservable<T>的”谓词”来对事件流做了一些操作。
- Select跟Linq操作有点类似,分别将两个按钮的事件变形为IObservable<int>(1)和IObservable<int>(-1);
- Merge操作将两个事件流合并为一个;
- Scan稍显复杂,对事件流做了一个折叠操作,给定了一个初始值,并通过一个函数来对结果和下一个值进行累加;
下面就让我们来看看IObservable<T>中常用的“谓词”
四、IObservable<T>中的谓词
IObservable<T>的灵感来源于LINQ,所以很多操作也跟LINQ中的操作差不多,例如Where、First、Last、Single、Max、Any。
还有一些“谓词”则是新出现的,例如上面提到的”Merge”、“Scan”等,为了理解这些“谓词”的含义,我们请出一个神器RxSandbox。
1、Merge操作,从下面的图中我们可以清晰的看出Merge操作将三个事件流中的事件合并在了同一个时间轴上。

2、Where操作则是根据指定的条件筛选出事件。

有了这个工具我们可以更加方便的了解这些“谓词”的用途。
五、IObservable<T>的创建
Observable类提供了很多静态方法用来创建IObservable<T>,之前的例子我们都使用FromEventPattern方法来将事件转化为IObservable<T>,接下来再看看别的方法。
Range方法可以产生一个指定范围内的IObservable<T>
|
1
2
|
Observable.Range(1, 10) .Subscribe(x => Console.WriteLine(x.ToString())); |
Interval方法可以每隔一定时间产生一个IObservable<T>:
|
1
2
|
Observable.Interval(TimeSpan.FromSeconds(1)) .Subscribe(x => Console.WriteLine(x.ToString())); |
Subscribe方法有一个重载,可以分别对Observable发生异常和Observable完成定义一个回调函数。
|
1
2
|
Observable.Range(1, 10) .Subscribe(x => Console.WriteLine(x.ToString()), e => Console.WriteLine("Error" + e.Message), () => Console.WriteLine("Completed")); |
还可以将IEnumerable<T>转化为IObservable<T>类型:
|
1
2
|
Enumerable.Range(1, 10).ToObservable() .Subscribe(x => Console.WriteLine(x.ToString())); |
也可以将IObservable<T>转化为IEnumerable<T>
|
1
|
var list= Observable.Range(1, 10).ToEnumerable(); |

六、其他Rx资源
除了.net中的Rx.net,其他语言也纷纷推出了自己的Rx框架。
Net中的反应式编程的更多相关文章
- Android中使用反应式编程RxJava
GitHut 地址: https://github.com/ReactiveX/RxAndroid (1)RxJava简介: RxJava 是一个在Java虚拟机上实现的响应式扩展库:提供了基于obs ...
- (转)Spring Boot 2 (十):Spring Boot 中的响应式编程和 WebFlux 入门
http://www.ityouknow.com/springboot/2019/02/12/spring-boot-webflux.html Spring 5.0 中发布了重量级组件 Webflux ...
- Spring Boot 2 (十):Spring Boot 中的响应式编程和 WebFlux 入门
Spring 5.0 中发布了重量级组件 Webflux,拉起了响应式编程的规模使用序幕. WebFlux 使用的场景是异步非阻塞的,使用 Webflux 作为系统解决方案,在大多数场景下可以提高系统 ...
- .NET 4.0 中的契约式编程
契约式编程不是一门崭新的编程方法论.C/C++ 时代早已有之.Microsoft 在 .NET 4.0 中正式引入契约式编程库.博主以为契约式编程是一种相当不错的编程思想,每一个开发人员都应该掌握.它 ...
- Java 中的链式编程
前言 在写项目的时候,有一个实体类有好多个属性,new 出来之后需要不停的使用setXXX( )方法,效率低而且代码可读性差,查询了下发现可以实现实体类的链式编程. public class Us ...
- 由表单验证说起,关于在C#中尝试链式编程的实践
在web开发中必不可少的会遇到表单验证的问题,为避免数据在写入到数据库时出现异常,一般比较安全的做法是前端会先做一次验证,通过后把数据提交到后端再验证一次,因为仅仅靠前端验证是不安全的,有太多的htt ...
- .Net中的反应式编程(Reactive Programming)
系列主题:基于消息的软件架构模型演变 一.反应式编程(Reactive Programming) 1.什么是反应式编程:反应式编程(Reactive programming)简称Rx,他是一个使用LI ...
- java中的链式编程
听到链式编程听陌生的,但是写出来就感觉其实很熟悉 package test; public class Test { String name; String phone; String mail; S ...
- 实现iOS中的链式编程
谈到链式编程,那Masonry几乎就是最经典的代表.如: make.top.equalTo(self.view).offset() 像这样top.equalTo(self.view).offset(6 ...
随机推荐
- python手记(47)
#!/usr/bin/env python # -*- coding: utf-8 -*- #http://blog.csdn.net/myhaspl #code:myhaspl@qq.com imp ...
- 关于在ios7之后改变状态栏颜色
看到网上都说 在ios7之后要这样设置 首先,须要在Info.plist配置文件里,添加键:UIViewControllerBasedStatusBarAppearance,并设置为YES: 然后,在 ...
- SWT的CheckBoxTreeView的上级菜单与下级菜单的选中的实现
是不是很神奇? treeViewer.addCheckStateListener(new ICheckStateListener() { @Override public void checkStat ...
- VS调试技巧之附加进程
用过VS一段时间的程序猿们相信都有过这种调试经历:每次按下F5进行断点调试时,都要等待好长时间:先让解决方式编译通过,然后启动VS自带的简版IIS作为server启动,进而开启浏览器,最后进行对应的操 ...
- 【leetcode】Candy(python)
题目要求的比它的邻居比自己奖励,因此,我们有最少一个多的.所有我们可以找到所有的坑,凹坑例如,存在以下三种情况. 找到全部的凹点后,我们就能够从凹点处開始向左右两个方向依次查找递增序列.当中每一个高的 ...
- Meet Apache Wicket
第一次接触Wicket,如此多的内容是文字,的原贴,希望大家指正 Meet Apache Wicket By JonathanLocke, original author of Wicket 乔纳森· ...
- SessionFactory的创建和Session的获得
1.当我们调用 Configuration config=new Configuration().configure(); 时候Hibernate会自己主动在当前的CLASSPATH中搜寻hibern ...
- 将鼠标移到文本弹出一些字幕CSS达到,不及格JS达到
使用css实施内容流行,否js代码,下面的代码,可直接使用复制, 需要背景图到下面的地址下载,谢谢! 住址:http://download.csdn.net/detail/zurich1979/722 ...
- 一些Android框架
从网上收集一些框架,敲代码偷懒这些框架非常实用,必须记下来,为了以后少写代码,用别人好的框架 ThinkAndroid ThinkAndroid(一个ThinkAndroid教程地址:http://m ...
- leetcode第一刷_Pow(x, n)
高速乘方的算法,写了好多变,出了各种错,真是服了我自己了. 思想是每次对n减半,将当前的temp平方.须要注意的是假设当前的n是个奇数,减半之后会丢失掉一次乘积,因此假设当前的n为奇数,应该先在结果里 ...