.NET 响应式编程 System.Reactive 系列文章(三):Subscribe 和 IDisposable 的深入理解


引言:为什么理解 Subscribe 和 IDisposable 很重要?

在前两篇文章中,我们详细介绍了 IObservable<T>IObserver<T> 的核心概念及交互流程。但在实际使用 System.Reactive 时,一个常见的误区是认为数据流一旦订阅,就不需要额外管理。这种认知是危险的,因为 Observable 的订阅可能是无限的,如果不管理好订阅的生命周期,很容易导致内存泄漏资源浪费

在 Rx 中,Subscribe() 方法返回一个 IDisposable 接口对象,用于手动取消订阅和释放资源。另外,System.Reactive 还提供了不返回 IDisposableSubscribe 重载,这些重载方法通过 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 方法签名

以下是其中一个不返回 IDisposableSubscribe 重载:

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 的重载

典型场景:

  1. 异步任务取消

    在异步任务中使用 CancellationToken 取消订阅数据流,避免阻塞或内存泄漏。

  2. 超时控制

    使用 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 可以简化订阅管理,但也有一些需要注意的地方:

  1. 不支持手动取消

    如果你使用的是返回 IDisposableSubscribe 方法,你可以手动调用 Dispose() 取消订阅。但如果你使用带 CancellationToken 的重载,就无法通过 Dispose() 取消订阅。

  2. 更适合一次性订阅

    CancellationTokenSubscribe 重载更适合一次性订阅的场景。如果你需要频繁管理多个订阅,使用 CompositeDisposable 或手动管理 IDisposable 可能更合适。


5. 两种订阅方式的对比

特性 返回 IDisposableSubscribe CancellationTokenSubscribe
是否支持手动取消订阅 支持 不支持
是否支持外部控制订阅生命周期 需要手动调用 Dispose() 通过 CancellationToken 控制
是否适合长期订阅 适合 更适合一次性订阅

6. Subscribe 和 IDisposable 的交互流程图

sequenceDiagram
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 的内部机制,并重点介绍了 CancellationTokenSubscribe 重载

  1. Subscribe() 方法返回 IDisposable,用于管理订阅的生命周期。
  2. 不返回 IDisposableSubscribe 重载,通过 CancellationToken 控制订阅的终止。
  3. 使用场景不同IDisposable 更适合长期订阅,CancellationToken 更适合一次性或外部控制的订阅。

下一篇文章预告

《.NET 响应式编程 System.Reactive 系列文章(四):操作符基础》

下一篇文章将介绍 System.Reactive 的基础操作符,包括如何创建转换过滤数据流。我们将通过实战示例,帮助你快速掌握 Rx 的操作符使用方法。敬请期待!

.NET 响应式编程 System.Reactive 系列文章(三):Subscribe 和 IDisposable 的深入理解的更多相关文章

  1. [转帖]浅谈响应式编程(Reactive Programming)

    浅谈响应式编程(Reactive Programming) https://www.jianshu.com/p/1765f658200a 例子写的非常好呢. 0.9312018.02.14 21:22 ...

  2. 响应式编程(Reactive Programming)(Rx)介绍

    很明显你是有兴趣学习这种被称作响应式编程的新技术才来看这篇文章的. 学习响应式编程是很困难的一个过程,特别是在缺乏优秀资料的前提下.刚开始学习时,我试过去找一些教程,并找到了为数不多的实用教程,但是它 ...

  3. 函数式响应式编程 - Functional Reactive Programming

    我们略过概念,直接看函数式响应式编程解决了什么问题. 从下面这个例子展开: 两个密码输入框,一个提交按钮. 密码.确认密码都填写并一致,允许提交:不一致提示错误. HTML 如下: <input ...

  4. 函数响应式编程及ReactiveObjC学习笔记 (三)

    之前讲了RAC如何帮我们实现KVO / 代理 / 事件 / 通知 今天先不去分析它的核心代码, 我们先看看ReactiveObjC库里面一些特别的东西,  如果大家点开ReactiveObjC目录应该 ...

  5. 响应式编程系列(一):什么是响应式编程?reactor入门

    响应式编程 系列文章目录 (一)什么是响应式编程?reactor入门 (二)Flux入门学习:流的概念,特性和基本操作 (三)Flux深入学习:流的高级特性和进阶用法 (四)reactor-core响 ...

  6. Java9第四篇-Reactive Stream API响应式编程

    我计划在后续的一段时间内,写一系列关于java 9的文章,虽然java 9 不像Java 8或者Java 11那样的核心java版本,但是还是有很多的特性值得关注.期待您能关注我,我将把java 9 ...

  7. Swift 响应式编程 浅析

    这里我讲一下响应式编程(Reactive Programming)是如何将异步编程推到一个全新高度的. 异步编程真的很难 大多数有关响应式编程的演讲和文章都是在展示Reactive框架如何好如何惊人, ...

  8. 函数响应式编程(FRP)框架--ReactiveCocoa

    由于工作原因,有段时间没更新博客了,甚是抱歉,只是,从今天開始我又活跃起来了,哈哈,于是决定每周更新一博.大家互相学习.交流. 今天呢.讨论一下关于ReactiveCocoa,这个採用函数响应式编程( ...

  9. 学习响应式编程 Reactor (1) - 响应式编程

    响应式编程 命令式编程(Imperative Programing),是一种描述计算机所需做出的行为的编程范式.详细的命令机器怎么(How)去处理以达到想要的结果(What). 声明式编程(Decla ...

  10. Project Reactor 响应式编程

    目录 一. 什么是响应式编程? 二. Project Reactor介绍 三. Reactor核心概念 Flux 1. just() 2. fromArray(),fromIterable()和 fr ...

随机推荐

  1. css动画效果(边框流光闪烁阴影效果)

    1.整体效果 https://mmbiz.qpic.cn/sz_mmbiz_gif/EGZdlrTDJa7odDQYuaatklJUMc5anU10PWLAt14rNnNUD6oHJG9U63fc0y ...

  2. C++泛型一:模板

    数据类型给程序设计带来的困扰及解决方案 int maxt(int, int); double maxt(double, double); 若有一种占位符T,能够代替类型,便可以简化代码的冗余编写 T ...

  3. Hadoop习题汇总

    目录 选择 单选 多选 判断 填空 简答 选择 单选 查看HDFS系统版本的Shell命令,以下正确的是(). hdfs -ver hdfs version (答案) dfsadmin version ...

  4. CF980-Div2-D

    CF980-Div2-D 题意 从 \(1\) 开始决策,若选当前数,则累计贡献 \(a[i]\) 并跳到 \(j\) 位置,\(j\) 是 \(\lt i\) 且没有决策过(包括选了和没选)的最大位 ...

  5. 教程:搭建一个我的世界模组服务器(Linux)

    首先给自己的服务器打个广告 服务器版本1.12.2 地址:www.verysucksminecraftserver.top(好像只有一个月) 所需Mod网盘:https://pan.quark.cn/ ...

  6. Mysql数据库笔记整理

    数据库-理论基础 1.什么是数据库? 数据:描述事物的符号记录,可以是数字.文字.图形.图像.声音.语言等,数据有多种形式,它们都可以经过数字化后存入计算机. 数据库:存储数据的仓库,是长期存放在计算 ...

  7. 牛客多校H题题解

    链接:[https://ac.nowcoder.com/acm/contest/81597/H] 来源:牛客网 题目描述 Red stands at the coordinate \((0,0)\) ...

  8. golang之性能分析工具pprof

    PProf 是一个 Go 程序性能分析工具,可以分析 CPU.内存等性能.Go 在语言层面上集成了 profile 采样工具,只需在代码中简单地引入 runtime/ppro 或者 net/http/ ...

  9. m4 mac mini本地部署ComfyUI,测试Flux-dev-GGUF的workflow模型10步出图,测试AI绘图性能,基于MPS(fp16),优点是能耗小和静音

    m4 mac mini已经发布了一段时间,针对这个产品,更多的是关于性价比的讨论,如果抛开各种补贴不论,价位上和以前发布的mini其实差别不大,真要论性价比,各种windows系统的mini主机的价格 ...

  10. Java基础 —— 集合(二)

    Collection 接口 Collection接口常用方法 boolean add(E e):在集合末尾添加元素 boolean remove(Object o):若集合中存在与o相同的元素,则删除 ...