使用Rx响应式编程管理CAD二次开发中的事件
Rx 简介
Rx 的全称是 Reactive Extensions,是一套用于处理事件流和异步数据流的编程模型。
在传统事件编程中,我们通常分别订阅事件、编写事件处理方法,并在适当的时候解除订阅。Rx 则把事件、定时器、异步结果等随时间产生的数据统一表示为数据流,再使用类似 LINQ 的方式进行处理。
可以把 Rx 简单理解为:
使用可组合的方式处理随时间不断产生的数据。
Rx学习成本较低,为了方便C#开发者使用,微软将大部分方法设计的和Linq非常相似,可以非常容易的入手。
安装起来也非常简单,只需要在nuget里引用System.Reactive包即可
核心概念
Rx 中最常见的几个概念如下:
IObservable<T>:表示可以被订阅的数据流。Subscribe():订阅数据流并接收数据。OnNext:数据流产生了一个新数据。OnError:数据流发生错误并结束。OnCompleted:数据流正常结束。IDisposable:表示一次订阅,调用Dispose()可以取消订阅。
Rx 的主要作用
Rx 比较适合处理以下问题:
- 监听连续发生的事件。
- 对事件进行过滤、转换和组合。
- 合并多个不同来源的数据流。
- 控制事件触发频率。
- 管理临时事件订阅的生命周期。
- 在对象不再使用时统一取消订阅,减少事件未解绑导致的内存泄漏。
Rx 常用操作符包括:
| 操作符 | 作用 |
|---|---|
Select | 转换数据 |
Where | 过滤数据 |
Take | 只接收指定数量的数据 |
Merge | 合并多个数据流 |
CombineLatest | 组合多个数据流的最新值 |
Throttle | 等待事件暂时停止后再处理 |
DistinctUntilChanged | 忽略与上一次相同的数据 |
简单示例
下面的代码创建一个数据流,并订阅其中的数据:
IObservable<int> numbers = Observable.Range(1, 5);
IDisposable subscription = numbers.Subscribe(value =>
{
Console.WriteLine(value);
});
subscription.Dispose();
Subscribe() 返回一个 IDisposable。当不再需要接收数据时,调用 Dispose() 即可结束本次订阅。
Rx 在CAD二次开发中的应用
Rx在CAD中适合处理事件,可用于管理临时事件,借助Rx中的类可以对事件更好的管理和拆除。
下面进行一些举例
例子1:使用Rx订阅PointMonitor事件。
在前面的文章中我们讲过Editor.PointMonitor事件。
其实cad中有很多其他类似的事件,如果你每次用的时候都要定义一个类似PointMonitorEventManager的容器封装一下,其实是件非常麻烦的事情,如果你的使用频率不高,那么使用Rx简化是一个不错的选择。也可以选择使用Rx进行封装。
Rx中将事件注册成数据流的方法有很多,由于CAD中的事件不遵循微软的 EventArgs<T> 标准,所以我们可以采用名字的方式来注册。
using var iDisposable = Observable.FromEventPattern(ed, nameof(ed.PointMonitor))
.Select(x => (PointMonitorEventArgs)x.EventArgs)
.Subscribe(args =>
{
// var pt = args.Context.ComputedPoint;
}
上述代码中在Subscribe订阅的时候,事件被注册,在iDisposable被释放时事件被拆除。
例子2:使用Rx订阅CommandWillStart事件
先上代码
private static IDisposable? documentManagerSubscribe;
public static void Init()
{
documentManagerSubscribe = Observable
.FromEventPattern(Acap.DocumentManager, nameof(Acap.DocumentManager.DocumentActivated))
.Select(x => x.EventArgs)
.OfType<DocumentCollectionEventArgs>()
.Select(x => x.Document)
.Subscribe(DocumentActivatedHandler);
DocumentActivatedHandler(Acap.DocumentManager.MdiActiveDocument);
}
private static readonly SerialDisposable _serialDisposable = new();
private static void DocumentActivatedHandler(Document? doc)
{
if(doc is null)
return;
_serialDisposable.Disposable = Observable.FromEventPattern(doc,nameof(doc.CommandWillStart))
.Select(x=>(CommandEventArgs)x.EventArgs)
.Publish()
.RefCount()
.Subscribe(x =>
{
try
{
var command = x.GlobalCommandName;
// do something
}
catch (Exception ex)
{
Log.Error("CommandWillStart事件发生错误" + ex.Message);
}
});
}
在Init里对DocumentManager文档管理器事件进行订阅,使其在文档被激活为活动文档时,执行DocumentActivatedHandler方法。
在DocumentActivatedHandler方法中对文档的CommandWillStart事件进行订阅,并在事件触发时执行某些操作。
documentManagerSubscribe变量的作用是保持事件的生命周期,在此变量销毁时事件将被Rx自动拆除,所以将其放在静态变量中持久化。
SerialDisposable是Rx中的一个单IDisposable接口管理容器,每次对其Disposable属性进行赋值时,它将自动将旧的对象进行释放(如果有),并将其赋上新值,这样非常符合我们的使用场景,有新的文档变成了活动文档,那么刚刚的活动文档就变成了非活动文档,正好通过此特性将旧文档上的事件进行拆除,这里不需要判断旧文档是否还在,Rx会帮你处理。
这里注意到,订阅CommandWillStart事件时使用了Publish和RefCount,他们起到的作用是:如果在程序的不同地方多次订阅了同一事件,那么Rx只会实际上在对象上订阅一次,再通过委托进行二次分发。
完
使用Rx响应式编程管理CAD二次开发中的事件
https://dyhcad.cn/archives/019f7f78-864d-72ac-8635-1ff83f13dee0
评论