场景

一个对象A,希望它的某些状态在发生改变时通知到B(或C、D),
常见的做法是在A中定义一个事件(或直接用委托),当状态改变时A去触发这个事件。而B直接订阅这个事件
 

这种设计有点问题
B由于要订阅A的事件,所以B得完全引用A,其实有时候没必要,因为我只关心A的状态变化而已
状态变更通知这种场景很多,有没有更通用的方式呢?

 

解决思路

有个谁说的碰到问题加个中间层就解决了,如果解决不了就再加一层


A和B都引用ChangeToken,
B向ChangeToken注册一个委托说:将来你有变化时回调我这个委托
当A的状态变化时会调用ChangeToken的一个方法,这个方法内部就会去触发执行B之前塞进去的委托
此时比如有组件C、D、E..都关心A的状态变化,也可以引用ChangeToken,并向其注册自己的委托
这样ChangeToken可以作为一个通用组件,在很多需要更改通知是场景中使用,如:asp.net core的配置系统、终结点路由、 ....

 

实现

微软定义了一个IChangeToken
HasChanged:表示当前这个ChangeToken是否变化过了
ActiveChangeCallbacks:当 A触发ChangeToken发生变化时是否主动回调B塞进来的委托
RegisterChangeCallback(Action<object> callback, object
state):提供一个方法,允许调用方塞入委托,B就是调用这个方法向ChangeToken塞入委托的。有一种情况是B希望在塞入委托的同时附带一个状态对象,将来委托被执行时这个状态对象作为执行委托的参数

CancellationChangeToken是一个用的比较多的实现类,它包含一个CancellationToken属性,这个属性是通过构造函数来初始化的(
CancellationTokenSource、CancellationToken自行查询相关资料),
简化的源码如下:
1 public class CancellationChangeToken : IChangeToken 2 { 3 public
CancellationChangeToken(CancellationToken cancellationToken) 4 { 5 Token =
cancellationToken; 6 } 7 8 public bool ActiveChangeCallbacks { get; private
set; } = true; 9 10 public bool HasChanged => Token.IsCancellationRequested; 11
12 private CancellationToken Token { get; } 13 14 public IDisposable
RegisterChangeCallback(Action<object> callback, object state) 15 { 16 return
Token.Register(callback, state);17 } 18 }

因为CancellationToken天然的已经现了IChangeToken,因此CancellationChangeToken只是对CancellationToken的包装。那为啥不直接让CancellationToken实现IChangeToken呢?我感觉是设计意图不同,CancellationToken设计时主要是考虑应用在取消异步操作这件事上的,只是碰巧取消异步操作与更改通知设计思路是相似的,所以才出现CancellationChangeToken

其它的实现类没去研究过,但是只要你对这种设计思路理解了,碰到其它实现类应该看看就明白了

例子

下面我们使用CancellationChangeToken来完成上面的A、B类,A类状态变化时
通知到B类(其实就是执行B类忘ChangeToken中塞入的委托),完整源码如下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5
Console.OutputEncoding = Encoding.UTF8; 6 CancellationTokenSource cts = new
CancellationTokenSource(); 7 CancellationChangeToken cct = new
CancellationChangeToken(cts.Token); 8 var a = new A(cts); 9 var b = new B(cct);
10 Console.ReadKey(); 11 } 12 } 13 14 public class A 15 { 16
CancellationTokenSource _cts;17 public A(CancellationTokenSource cts) 18 { 19
this._cts = cts; 20 Task.Run(() => 21 { 22 Task.Delay(2000).Wait(); 23
Console.WriteLine("模拟触发更改通知"); 24 _cts.Cancel(); 25 }); 26 } 27 } 28 public
class B 29 { 30 public B(CancellationChangeToken cct) { 31 object testState = 1
;32 cct.RegisterChangeCallback(obj => { 33 //将来cct检测到变化时此委托会被执行 34 //
obj是注册委托是传递进来的参数,就是这里的testState 35 Console.WriteLine($"状态变化了,状态值{obj}"); 36 },
testState);37 } 38 }
上面只是演示IChangeToken的思路,asp.net core中源码的应用时通常是在A中提供一个返回IChangeToken的方法

无限监控与ChangeToken.OnChange

上面的方式只能变更通知一次,下面可以永远监控
1 class Program 2 { 3 static void Main(string[] args) 4 { 5
Console.OutputEncoding = Encoding.UTF8; 6 var a = new A(); 7 //
实现无限监控状态变化。OnChange有两个委托类型的参数,我们分别称为委托1和委托2 8 ChangeToken.OnChange(() =>
a.CreateChangeToken(), () => Console.WriteLine("a状态变化了")); 9
Console.ReadKey();10 } 11 } 12 13 public class A 14 { 15
CancellationTokenSource _cts;16 public A() 17 { 18 Task.Run(() => 19 { 20
while (true) 21 { 22 Task.Delay(2000).Wait(); 23 Console.WriteLine("
模拟两秒一次触发一次状态修改通知"); 24 this._cts.Cancel(); 25 } 26 }); 27 } 28 29 public
IChangeToken CreateChangeToken() {30 _cts = new CancellationTokenSource(); 31
return new CancellationChangeToken(_cts.Token); 32 } 33 }
重点是这句:ChangeToken.OnChange(() => a.CreateChangeToken(), () =>
Console.WriteLine("a状态变化了"));

OnChange有两个委托类型的参数,我们分别称为委托1和委托2,当a的状态变化后会执行委托2,之后会执行委托1,当a状态又变化时又会执行委托2,之后执行委托1,如此往复实现无限监控 

这是今天学习的内容,可能理解得不是很准确,仅供参考...