.NET 响应式编程 System.Reactive 系列文章(二):深入理解 IObservable<T> 和 IObserver<T>
.NET 响应式编程 System.Reactive 系列文章(二):深入理解 IObservable<T> 和 IObserver<T>
引言:为什么我们调整了学习顺序?
在上一篇文章的结尾,我原本计划在本篇介绍 System.Reactive 的基础操作符,比如如何创建、转换和过滤数据流。但在撰写内容时,我意识到,对于刚接触 System.Reactive 的读者来说,直接介绍操作符可能有些仓促,因为 操作符的使用必须建立在对 IObservable<T> 和 IObserver<T> 这两个核心接口的深刻理解之上。
正如在传统编程中,你需要先理解 集合(Collection) 和 迭代器(Iterator) 的本质,才能更好地使用 LINQ 操作符一样。而在 Rx 中,IObservable<T> 是数据流的生产者,IObserver<T> 是数据流的消费者,理解这两个接口是掌握 Rx 的基础。
因此,我决定调整顺序,在本篇文章中,深入介绍 IObservable<T> 和 IObserver<T> 的核心概念、方法和使用方式,为后续学习操作符打下坚实的基础。
IObservable<T> 和 IObserver<T> 的关系
在 Rx 中,数据流的生产和消费是通过 观察者模式(Observer Pattern) 实现的。这种模式定义了两种角色:
IObservable<T>(可观察对象/数据流的生产者)IObserver<T>(观察者/数据流的消费者)
二者的关系可以简单理解为:
IObservable<T>负责“推送”数据项。IObserver<T>负责“接收”数据项。
订阅(Subscribe) 是连接这两者的桥梁。当 IObserver<T> 订阅一个 IObservable<T> 时,数据流开始传递。
1. IObservable<T> 的定义和职责
IObservable<T> 接口定义
public interface IObservable<out T>
{
IDisposable Subscribe(IObserver<T> observer);
}
IObservable<T> 的职责:
- 代表一个数据流,它可以产生零个、一个或多个数据项。
- 当一个观察者(
IObserver<T>)订阅这个数据流时,它会调用Subscribe方法,并开始推送数据。 - 数据流可能会因为正常完成或发生错误而终止。
2. IObserver<T> 的定义和职责
IObserver<T> 接口定义
public interface IObserver<in T>
{
void OnNext(T value);
void OnError(Exception error);
void OnCompleted();
}
IObserver<T> 的职责:
- 代表一个数据的消费者,它对
IObservable<T>提供的数据流做出响应。 IObserver<T>需要实现三个方法:OnNext(T value):当有新的数据项时调用。OnError(Exception error):当数据流发生错误时调用。OnCompleted():当数据流正常结束时调用。
3. IObservable<T> 和 IObserver<T> 的交互流程
让我们通过一个实际的交互流程图来直观地理解 IObservable<T> 和 IObserver<T> 的关系:
- 观察者(Observer)通过
Subscribe方法订阅可观察对象(Observable)。 - 可观察对象(Observable)调用 Observer 的
OnNext方法推送数据。 - 如果发生错误,可观察对象(Observable)调用
OnError方法终止数据流。 - 如果数据流正常结束,可观察对象(Observable)调用
OnCompleted方法终止数据流。
participant Observable as IObservable<T>
participant Observer as IObserver<T>
Observer ->> Observable: Subscribe()
Observable ->> Observer: OnNext(T value)
Observable ->> Observer: OnNext(T value)
alt 数据流正常结束
Observable ->> Observer: OnCompleted()
else 发生错误
Observable ->> Observer: OnError(Exception error)
end
4. 示例代码:实现一个简单的 Observable 和 Observer
为了更好地理解这两个接口,我们从零开始,手动实现一个简单的 IObservable 和 IObserver。
实现自定义 Observable
using System;
using System.Threading;
using System.Threading.Tasks;
public sealed class SimpleObservable : IObservable<int>
{
IDisposable IObservable<int>.Subscribe(IObserver<int> observer)
{
SimpleDisposable disposable = new();
Task.Run(() =>
{
// 模拟数据的生产,以及假设每次生产都需要时间,消费者可以随时调用Dispose方法取消订阅
for (int i = 1; i <= 5; i++)
{
if (disposable.IsDisposed)
{
return;
}
observer.OnNext(i);
// 模拟产生数据需要耗时50毫秒
Thread.Sleep(50);
}
observer.OnCompleted();
});
return disposable;
}
private sealed class SimpleDisposable : IDisposable
{
internal bool IsDisposed { get; private set; }
void IDisposable.Dispose()
{
IsDisposed = true;
Console.WriteLine("Subscription disposed.");
}
}
}
实现自定义 Observer
using System;
public sealed class SimpleObserver : IObserver<int>
{
void IObserver<int>.OnNext(int value) => Console.WriteLine($"Received: {value}");
void IObserver<int>.OnError(Exception error) => Console.WriteLine($"Error: {error.Message}");
void IObserver<int>.OnCompleted() => Console.WriteLine("Sequence Completed.");
}
订阅和运行
using System;
using System.Threading;
class Program
{
static void Main(string[] args)
{
IObservable<int> observable = new SimpleObservable();
IObserver<int> observer = new SimpleObserver();
IDisposable subscription = observable.Subscribe(observer);
// 模拟消费数据100毫秒后取消订阅
Thread.Sleep(100);
subscription.Dispose();
}
}
输出结果:
Received: 1
Received: 2
Subscription disposed.
5. 常见问题解答
Q1:为什么 Subscribe 方法返回 IDisposable?
Subscribe 方法返回一个 IDisposable 对象,允许订阅者在不再需要数据流时取消订阅,以释放资源,避免内存泄漏。
Q2:OnError 和 OnCompleted 可以同时调用吗?
不能。数据流要么以错误终止,要么正常结束,二者是互斥的。
Q3:IObservable<T> 可以被多个 IObserver<T> 订阅吗?
可以。一个 IObservable<T> 可以被多个观察者订阅,每个观察者都会接收到数据流的推送。
总结
在本篇文章中,我们深入探讨了 IObservable<T> 和 IObserver<T> 这两个核心接口的定义和职责,并通过代码示例展示了它们如何交互。
核心要点:
IObservable<T>是数据流的生产者,它负责推送数据。IObserver<T>是数据流的消费者,它负责接收和处理数据。Subscribe方法将生产者和消费者连接起来,并返回一个IDisposable对象,用于取消订阅。
下一篇文章预告
《.NET 响应式编程 System.Reactive 系列文章(三):Subscribe 和 IDisposable 的深入理解》
在下一篇文章中,我们将重点探讨Subscribe方法的内部工作机制、IDisposable的作用,以及如何优雅地管理订阅的生命周期。敬请期待!
.NET 响应式编程 System.Reactive 系列文章(二):深入理解 IObservable<T> 和 IObserver<T>的更多相关文章
- 响应式编程(Reactive Programming)(Rx)介绍
很明显你是有兴趣学习这种被称作响应式编程的新技术才来看这篇文章的. 学习响应式编程是很困难的一个过程,特别是在缺乏优秀资料的前提下.刚开始学习时,我试过去找一些教程,并找到了为数不多的实用教程,但是它 ...
- [转帖]浅谈响应式编程(Reactive Programming)
浅谈响应式编程(Reactive Programming) https://www.jianshu.com/p/1765f658200a 例子写的非常好呢. 0.9312018.02.14 21:22 ...
- 函数式响应式编程 - Functional Reactive Programming
我们略过概念,直接看函数式响应式编程解决了什么问题. 从下面这个例子展开: 两个密码输入框,一个提交按钮. 密码.确认密码都填写并一致,允许提交:不一致提示错误. HTML 如下: <input ...
- 函数响应式编程及ReactiveObjC学习笔记 (二)
之前我们初步认识了RAC的设计思路跟实现方式, 现在我们再来看看如果使用它以及它能帮我们做什么 One of the major advantages of RAC is that it provid ...
- 响应式编程系列(一):什么是响应式编程?reactor入门
响应式编程 系列文章目录 (一)什么是响应式编程?reactor入门 (二)Flux入门学习:流的概念,特性和基本操作 (三)Flux深入学习:流的高级特性和进阶用法 (四)reactor-core响 ...
- Swift 响应式编程 浅析
这里我讲一下响应式编程(Reactive Programming)是如何将异步编程推到一个全新高度的. 异步编程真的很难 大多数有关响应式编程的演讲和文章都是在展示Reactive框架如何好如何惊人, ...
- 函数响应式编程(FRP)框架--ReactiveCocoa
由于工作原因,有段时间没更新博客了,甚是抱歉,只是,从今天開始我又活跃起来了,哈哈,于是决定每周更新一博.大家互相学习.交流. 今天呢.讨论一下关于ReactiveCocoa,这个採用函数响应式编程( ...
- 学习响应式编程 Reactor (1) - 响应式编程
响应式编程 命令式编程(Imperative Programing),是一种描述计算机所需做出的行为的编程范式.详细的命令机器怎么(How)去处理以达到想要的结果(What). 声明式编程(Decla ...
- 函数响应式编程(FRP)—基础概念篇
原文出处:http://ios.jobbole.com/86815/. 一函数响应式编程 说到函数响应式编程,就不得不提到函数式编程,他们俩有什么关系呢?今天我们就详细的解析一下他们的关系. 现在下面 ...
- RxJS入门之函数响应式编程
一.函数式编程 1.声明式(Declarativ) 和声明式相对应的编程⽅式叫做命令式编程(ImperativeProgramming),命令式编程也是最常见的⼀种编程⽅式. //命令式编程: fun ...
随机推荐
- MVC PHP架构 博客论坛实现全过程
目录 1. MVC的历史 1.1 优点与缺点 1.1.1 优点 1.1.2 缺点 2. 个人博客论坛的MVC实现 2.1 前言 2.2 web代码结构 框架 2.2.1 web应用发展 2.2.2 C ...
- Selenium测试form表单之checkbox和radio
一.定义form表单 用到的元素:checkbox和radiobutton 下图定义了一个选择爱好和选择性别的form表单,区域1用到的表单元素是checkbox(复选框),区域2用到的表单元素是ra ...
- 基于Ubuntu搭建Pwn调试环境
Pwn环境配置 本文演示使用干净的Vmware下安装的的 Ubuntu 18.04 LTS镜像 配置以下Pwn环境: OS(系统)配置 VMware Tools net-tools open-vm-t ...
- php xattr操作文件扩展属性再续
今天偶然发现自己电脑还有一个隐藏硬盘,500G的我平时没挂载,就没用到,然后这次就给它挂载了,然后发现读取文件,操作xattr都很慢,比之前速度慢10倍左右,猜测可能是固态硬盘和机械硬盘的差别关系.看 ...
- MySQL原理简介—1.SQL的执行流程
大纲(2426字) 1.MySQL驱动的作用 2.Java系统中的数据库连接池的作用 3.MySQL中的数据库连接池的作用 4.网络连接必须让线程来处理 5.SQL接口会负责处理接收到的SQL语句 6 ...
- 域渗透之利用WMI来横向渗透
目录 前言 wmi介绍 wmiexec和psexec的区别 wmic命令执行 wmiexec.vbs wmiexec.py Invoke-WmiCommand.ps1 前言 上一篇打红日靶场拿域控是用 ...
- 微信小游戏sdk接入支付和登录,解决了wx原生不支持ios支付的痛点
前情提要 微信小游戏是小程序的一种. 项目接入微信小游戏sdk的支付和登录.主要难点在于接入ios的支付.因为官方只支持android, 不支持ios. 即ios用户不能直接在小游戏中发起支付,参考市 ...
- Codeforces Round 960 (Div.2)
A 非常容易观察到性质,注意 Alice 为先手,发现当 \(a_{\max}\) 的个数为奇数时显然能 win,但如果 \(a_{\max}\) 的个数为偶数且有一个数具有奇数个可以作为跳板,那么也 ...
- scikit-learn中的Pipeline:构建高效、可维护的机器学习流程
我们使用scikit-learn进行机器学习的模型训练时,用到的数据和算法参数会根据具体的情况相应调整变化, 但是,整个模型训练的流程其实大同小异,一般都是加载数据,数据预处理,特征选择,模型训练等几 ...
- 中电金信:ChatGPT一夜爆火,知识图谱何以应战?
随着ChatGPT的爆火出圈 人工智能再次迎来发展小高潮 那么作为此前搜索领域的主流技术 知识图谱前路又将如何呢? 事实上,ChatGPT也 ...