.NET 响应式编程 System.Reactive 系列文章(三):Subscribe 和 IDisposable 的深入理解
.NET 响应式编程 System.Reactive 系列文章(三):Subscribe 和 IDisposable 的深入理解
引言:为什么理解 Subscribe 和 IDisposable 很重要?
在前两篇文章中,我们详细介绍了 IObservable<T> 和 IObserver<T> 的核心概念及交互流程。但在实际使用 System.Reactive 时,一个常见的误区是认为数据流一旦订阅,就不需要额外管理。这种认知是危险的,因为 Observable 的订阅可能是无限的,如果不管理好订阅的生命周期,很容易导致内存泄漏和资源浪费。
在 Rx 中,Subscribe() 方法返回一个 IDisposable 接口对象,用于手动取消订阅和释放资源。另外,System.Reactive 还提供了不返回 IDisposable 的 Subscribe 重载,这些重载方法通过 CancellationToken 管理订阅的生命周期。在本篇文章中,我们将深入探讨 Subscribe 和 IDisposable 的原理、这些特殊重载的设计原因,以及在实际使用中的应用场景。
1. Subscribe 的内部机制
1.1 Subscribe 的作用
Subscribe 是连接 IObservable<T> 和 IObserver<T> 的桥梁。当你调用 Subscribe() 方法时:
IObservable<T>开始向IObserver<T>推送数据。- 订阅会保持活跃状态,直到:
- 数据流结束(调用
OnCompleted())。 - 发生错误(调用
OnError())。 - 手动取消订阅(调用
Dispose())。 - 超时取消订阅(向CancellationToken注册超时回调)。
- 数据流结束(调用
1.2 为什么 Subscribe 返回 IDisposable?
普通的 Subscribe 重载 返回一个 IDisposable 对象,允许你通过调用 Dispose() 方法取消订阅。这是管理数据流生命周期的核心机制之一。
2. Subscribe 重载:不返回 IDisposable 的特殊情况
System.Reactive 提供了一些特殊的 Subscribe 重载方法,它们不返回 IDisposable,而是依赖于 CancellationToken 来控制订阅的生命周期。这些方法设计的目的是为了提供一种外部取消订阅的机制,让你无需手动管理 Dispose() 的调用。
2.1 方法签名
以下是其中一个不返回 IDisposable 的 Subscribe 重载:
public static void Subscribe<T>(
this IObservable<T> source,
Action<T> onNext,
Action<Exception> onError,
Action onCompleted,
CancellationToken cancellationToken
);
这种重载方法的使用场景是:你希望通过 CancellationToken 来控制订阅的生命周期,而不是手动调用 Dispose()。
2.2 示例代码:使用 CancellationToken 管理订阅
示例:超时取消订阅
using System;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
IObservable<long> observable = Observable.Interval(TimeSpan.FromSeconds(1));
CancellationTokenSource cts = new();
// 使用 Subscribe 方法并传入 CancellationToken
observable.Subscribe(
onNext: static value => Console.WriteLine($"Received: {value}"),
onError: static ex => Console.WriteLine($"Error: {ex.Message}"),
onCompleted: static () => Console.WriteLine("Completed"),
token: cts.Token
);
// 模拟运行 5 秒后取消订阅
Console.WriteLine("Running for 5 seconds...");
Thread.Sleep(5000);
cts.Cancel();
Console.WriteLine("Subscription cancelled.");
}
}
输出结果:
Running for 5 seconds...
Received: 0
Received: 1
Received: 2
Received: 3
Subscription cancelled.
2.3 使用场景:什么时候使用 CancellationToken?
| 使用场景 | 推荐的 Subscribe 重载 |
|---|---|
| 需要手动取消订阅 | 返回 IDisposable 的重载 |
| 使用外部控制(如用户交互、超时)控制订阅 | 带 CancellationToken 的重载 |
典型场景:
异步任务取消
在异步任务中使用CancellationToken取消订阅数据流,避免阻塞或内存泄漏。超时控制
使用CancellationTokenSource.CancelAfter()设置超时取消订阅。
2.4 示例:设置超时取消订阅
using System;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
IObservable<long> observable = Observable.Interval(TimeSpan.FromSeconds(1));
CancellationTokenSource cts = new();
cts.CancelAfter(TimeSpan.FromSeconds(3)); // 设置 3 秒后自动取消订阅
observable.Subscribe(
onNext: static value => Console.WriteLine($"Received: {value}"),
onError: static ex => Console.WriteLine($"Error: {ex.Message}"),
onCompleted: static () => Console.WriteLine("Completed"),
token: cts.Token
);
Console.WriteLine("Running...");
Thread.Sleep(5000);
Console.WriteLine("Program ended.");
}
}
输出结果:
Running...
Received: 0
Received: 1
Received: 2
Program ended.
3. 使用场景总结
| 使用方式 | 特点 | 适用场景 |
|---|---|---|
Subscribe 返回 IDisposable |
允许手动取消订阅 | 长时间订阅或频繁管理多个订阅 |
Subscribe 接受 CancellationToken |
通过外部控制(如超时或用户交互)取消订阅 | 异步任务、超时控制、用户交互场景 |
4. 注意事项:CancellationToken 的局限性
虽然使用 CancellationToken 可以简化订阅管理,但也有一些需要注意的地方:
不支持手动取消
如果你使用的是返回IDisposable的Subscribe方法,你可以手动调用Dispose()取消订阅。但如果你使用带CancellationToken的重载,就无法通过Dispose()取消订阅。更适合一次性订阅
带CancellationToken的Subscribe重载更适合一次性订阅的场景。如果你需要频繁管理多个订阅,使用CompositeDisposable或手动管理IDisposable可能更合适。
5. 两种订阅方式的对比
| 特性 | 返回 IDisposable 的 Subscribe |
带 CancellationToken 的 Subscribe |
|---|---|---|
| 是否支持手动取消订阅 | 支持 | 不支持 |
| 是否支持外部控制订阅生命周期 | 需要手动调用 Dispose() |
通过 CancellationToken 控制 |
| 是否适合长期订阅 | 适合 | 更适合一次性订阅 |
6. Subscribe 和 IDisposable 的交互流程图
participant Observer as IObserver<T>
participant Observable as IObservable<T>
participant IDisposable as IDisposable
Observer ->> Observable: Subscribe()
Observable ->> Observer: OnNext(T value)
Observable ->> Observer: OnNext(T value)
Observer ->> IDisposable: Dispose()
Observable -->> Observer: 停止推送数据
总结
在本篇文章中,我们详细探讨了 Subscribe 和 IDisposable 的内部机制,并重点介绍了 带 CancellationToken 的 Subscribe 重载:
Subscribe()方法返回IDisposable,用于管理订阅的生命周期。- 不返回
IDisposable的Subscribe重载,通过CancellationToken控制订阅的终止。 - 使用场景不同:
IDisposable更适合长期订阅,CancellationToken更适合一次性或外部控制的订阅。
下一篇文章预告
《.NET 响应式编程 System.Reactive 系列文章(四):操作符基础》
下一篇文章将介绍 System.Reactive 的基础操作符,包括如何创建、转换和过滤数据流。我们将通过实战示例,帮助你快速掌握 Rx 的操作符使用方法。敬请期待!
.NET 响应式编程 System.Reactive 系列文章(三):Subscribe 和 IDisposable 的深入理解的更多相关文章
- [转帖]浅谈响应式编程(Reactive Programming)
浅谈响应式编程(Reactive Programming) https://www.jianshu.com/p/1765f658200a 例子写的非常好呢. 0.9312018.02.14 21:22 ...
- 响应式编程(Reactive Programming)(Rx)介绍
很明显你是有兴趣学习这种被称作响应式编程的新技术才来看这篇文章的. 学习响应式编程是很困难的一个过程,特别是在缺乏优秀资料的前提下.刚开始学习时,我试过去找一些教程,并找到了为数不多的实用教程,但是它 ...
- 函数式响应式编程 - Functional Reactive Programming
我们略过概念,直接看函数式响应式编程解决了什么问题. 从下面这个例子展开: 两个密码输入框,一个提交按钮. 密码.确认密码都填写并一致,允许提交:不一致提示错误. HTML 如下: <input ...
- 函数响应式编程及ReactiveObjC学习笔记 (三)
之前讲了RAC如何帮我们实现KVO / 代理 / 事件 / 通知 今天先不去分析它的核心代码, 我们先看看ReactiveObjC库里面一些特别的东西, 如果大家点开ReactiveObjC目录应该 ...
- 响应式编程系列(一):什么是响应式编程?reactor入门
响应式编程 系列文章目录 (一)什么是响应式编程?reactor入门 (二)Flux入门学习:流的概念,特性和基本操作 (三)Flux深入学习:流的高级特性和进阶用法 (四)reactor-core响 ...
- Java9第四篇-Reactive Stream API响应式编程
我计划在后续的一段时间内,写一系列关于java 9的文章,虽然java 9 不像Java 8或者Java 11那样的核心java版本,但是还是有很多的特性值得关注.期待您能关注我,我将把java 9 ...
- Swift 响应式编程 浅析
这里我讲一下响应式编程(Reactive Programming)是如何将异步编程推到一个全新高度的. 异步编程真的很难 大多数有关响应式编程的演讲和文章都是在展示Reactive框架如何好如何惊人, ...
- 函数响应式编程(FRP)框架--ReactiveCocoa
由于工作原因,有段时间没更新博客了,甚是抱歉,只是,从今天開始我又活跃起来了,哈哈,于是决定每周更新一博.大家互相学习.交流. 今天呢.讨论一下关于ReactiveCocoa,这个採用函数响应式编程( ...
- 学习响应式编程 Reactor (1) - 响应式编程
响应式编程 命令式编程(Imperative Programing),是一种描述计算机所需做出的行为的编程范式.详细的命令机器怎么(How)去处理以达到想要的结果(What). 声明式编程(Decla ...
- Project Reactor 响应式编程
目录 一. 什么是响应式编程? 二. Project Reactor介绍 三. Reactor核心概念 Flux 1. just() 2. fromArray(),fromIterable()和 fr ...
随机推荐
- uniapp、nativeJS、H5+退出APP应用(IOS+安卓)
uniapp.nativeJS.H5+退出APP应用(IOS+安卓)阅读原文:https://mp.weixin.qq.com/s/Aru-DCcSHrNcuxJ6Q94QLQ直接扫码进入此链接可阅读 ...
- python项目实战——一元线性回归预测模型
文章目录 1.一元线性回归简介 2.环境准备 3.数据准备 4.可视化数据 5.构建线性回归模型 在数据科学领域,预测分析是一项核心技能.一元线性回归作为预测分析的基石,能够帮助我们理解一个自变量如何 ...
- 每日学学Java开发规范,集合处理(附阿里巴巴Java开发手册(终极版))
前言 每次去不同的公司,码不同的代码,适应不同的规范,经常被老大教育规范问题,我都有点走火入魔的感觉,还是要去看看阿里巴巴Java开发规范,从中熟悉一下,纠正自己,码出高效,码出质量. 想细看的可以去 ...
- 什么DevOps方法论?
最近项目组事情越来越多,人员管理和项目事项管理成为了重点关注的问题,无意间听到同事间讨论DevOps方法论可以有效提升项目管理能力,实现组织精益化管理,运维一体化.于是我上网查了一下"Dev ...
- Oracle-表分析和索引分析解读
概述当表没有做分析的时候,Oracle 会使用动态采样来收集统计信息. 获取准确的段对象(表,表分区,索引等)的分析数据,是CBO存在的基石,CBO的机制就是收集尽可能多的对象信息和系统信息,通过对这 ...
- HJ17 坐标移动问题 ——秋招笔试
HJ17 坐标移动问题 华为笔试[难度中等] 问题描述: 开发一个坐标计算工具, A表示向左移动,D表示向右移动,W表示向上移动,S表示向下移动.从(0,0)点开始移动,从输入字符串里面读取一些坐标, ...
- PostgreSQL加密连接SSL配置
PostgreSQL加密连接SSL配置 环境说明 操作系统 主机名 IP 类型 说明 CentOS Linux release 7.6.1810 (Core) centos7.6 192.168.1. ...
- 【一步步开发AI运动小程序】十九、运动识别中如何解析RGBA帧图片?
引言 最近有不少开发者向我们咨询,像体测.赛事等应用场景中,需要保存运动过程的图像,如何将相机抽取的RGBA帧图像解析成.jpg或.png格式的图像?今天我们就为您介绍相应的解决方案. 一.RGBA图 ...
- Java实时多任务调度过程中的安全监控设计
方浩波 (fanghb@eastcom.com)东方通信网络研究所 简介: 在一系列关联的多任务的实时环境中,如果有一个任务发生失败,可能导致所有任务产生连锁反应,从而造成调度失控的局面.特别是对于核 ...
- Javascript 粘贴板
1.前言 本节讲述如何封装一个操作粘贴板的方法 原理:选中某个Dom元素(比如文本域),执行区域复制命令即可. 相关API:document.execCommand():该方法允许运行命令来操纵可编辑 ...