一个对象A,希望它的某些状态在发生改变时通知到B(或C、D)。常见的做法是在A中定义一个事件(或委托),当状态改变时A去触发这个事件。而B直接订阅这个事件 这种设计有点问题,B由于要订阅A的事件,所以B得完全引用A,其实有时候没必要,因为我只关心A的状态变化而已 解决方案就是加个中间层ChangeToken,A、B都引用ChangeToken,A通知ChangeToken状态改变,B向ChangeToken注册委托 这样ChangeToken可以作为一个通用组件,在很多需要更改通知是场景中使用,如:asp.net core的配置系统、终结点路由、 ....
微软定义了一个IChangeToken HasChanged:表示当前这个ChangeToken是否变化过了 ActiveChangeCallbacks:当 A触发ChangeToken发生变化时是否主动回调B塞进来的委托 RegisterChangeCallback(Action callback, object state):提供一个方法,允许调用方塞入委托,B就是调用这个方法向ChangeToken塞入委托的。有一种情况是B希望在塞入委托的同时附带一个状态对象,将来委托被执行时这个状态对象作为执行委托的参数 在“物理文件”这个圈子里面,IChangeToken的真身叫做PollingFileChangeToken 在“配置系统”这个圈子里面,IChangeToken的真身叫做ConfigurationReloadToken 如果不需要IChangeToken可以使用NullChangeToken.Singleton CancellationChangeToken CancellationChangeToken是一个用的比较多的实现类,它包含一个CancellationToken属性,这个属性是通过构造函数来初始化的,简化代码如下: public class CancellationChangeToken : IChangeToken { public CancellationChangeToken(CancellationToken cancellationToken) { Token = cancellationToken; } public bool ActiveChangeCallbacks { get; private set; } = true; public bool HasChanged => Token.IsCancellationRequested; private CancellationToken Token { get; } public IDisposable RegisterChangeCallback(Action<object> callback, object state) { return Token.Register(callback, state); } } CancellationChangeToken只是对CancellationToken的包装。那为啥不直接让CancellationToken实现IChangeToken呢?我感觉是设计意图不同,CancellationToken设计时主要是考虑应用在取消异步操作这件事上的,只是碰巧取消异步操作与更改通知设计思路是相似的,所以才出现CancellationChangeToken CompositeChangeToken CompositeChangeToken代表由多个IChangeToken组合而成的复合型IChangeToken对象。如下面的代码片段所示,我们在调用构造函数创建一个CompositeChangeToken对象的时候,需要提供这些IChangeToken对象。对于一个CompositeChangeToken对象来说,只要组成它的任何一个IChangeToken发生改变,其HasChanged属性将会变成True,而注册的回调自然会被执行。至于ActiveChangeCallbacks属性,只要任何一个IChangeToken的同名属性返回True,该属性就会返回True。 public class CompositeChangeToken : IChangeToken { public bool ActiveChangeCallbacks { get; } public IReadOnlyList<IChangeToken> ChangeTokens { get; } public bool HasChanged { get; } public CompositeChangeToken(IReadOnlyList<IChangeToken> changeTokens); public IDisposable RegisterChangeCallback(Action<object> callback, object state); } IChangeToken演示 static void Main(string[] args) { A a = new A(); IChangeToken changeToken = a.CreateToken(); B b = new B(changeToken); C c = new C(changeToken); Console.ReadKey(); } public class A { CancellationTokenSource _tokenSource = null; public A() { Task.Run(() => { while (true) { Task.Delay(1000).Wait(); Console.WriteLine("A状态已修改"); this.ChangeState(); } }); } public void ChangeState() { _tokenSource.Cancel(); } public IChangeToken CreateToken() { _tokenSource = new CancellationTokenSource(); return new CancellationChangeToken(_tokenSource.Token); } } public class B { public B(IChangeToken changeToken) { changeToken.RegisterChangeCallback((state) => { Console.WriteLine("B收到通知"); }, null); } } public class C { public C(IChangeToken changeToken) { changeToken.RegisterChangeCallback((state) => { Console.WriteLine("C收到通知"); }, null); } } A轮询修改状态,但是B、C只会调用一次。需要借助ChangeToken,可以永远监控A状态 static void Main(string[] args) { A a = new A(); ChangeToken.OnChange(() => a.CreateToken(), () => { Console.WriteLine("监听到A状态已修改"); }); Console.ReadKey(); } 搞懂了IChangeToken我们就很轻松就能理解了ChangeToken,作为静态类的它,肯定是作为一个工具类的实现。 ChangeToken.OnChange( () => physicalFileProvider.Watch("*.*"), () => Console.WriteLine("检测到文件夹有变化!") ); 那么您可能会说,我直接使用pysicalFileProvider.Watch()方法返回的IChangeToken的RegisterChangeCallback方法订阅不行吗?他们有什么区别吗? 答案是:“调用次数”。使用RegisterChangeCallback的方法,只会执行一次回调内容,因为当“令牌”用了一次之后,其实它就失效了。所以上面那个监控文件改动的代码,当第二次文件改动的时候,它其实是不会再执行回调的。 而使用ChangeToken这个静态类,它就可以帮助您不断的去获取新“令牌”然后注册对应的回调,所以就能够保证咱们多次改变也能触发回调了。 () => physicalFileProvider.Watch("*.*")这部分代码我们可以称它为“令牌生产过程”,而() => Console.WriteLine("检测到文件夹有变化!")叫做“令牌的消费过程”。 ChangeToken 干的事情就是:当消费者消费之后,就又会去让“生产过程”再生成一个令牌出来,并且在该令牌上挂载“消费过程”,这样就能保证能够一直“观察”下去了。 public static void Main(string[] args) { string dir = Directory.GetCurrentDirectory(); string fileName = Path.Combine(dir, "a.txt"); if (!File.Exists(fileName)) { var fs = File.Create(fileName); fs.Dispose(); } PhysicalFileProvider fileProvider = new PhysicalFileProvider(dir); IChangeToken changeToken = fileProvider.Watch("*.*"); //只会执行一次 changeToken.RegisterChangeCallback((state) => { Console.WriteLine("1文件被修改"); },"fan"); //每次修改都会执行(正确方式) ChangeToken.OnChange(()=>fileProvider.Watch("*.*"), () => { Console.WriteLine("2文件被修改"); }); Console.ReadKey(); } 案例1:添加缓存时提供IChangeToken,当依赖文件修改时,删除缓存 var fileProvider = new PhysicalFileProvider(Path.GetDirectoryName(dependencyFile)); var changeToken = fileProvider.Watch(Path.GetFileName(dependencyFile)); cache.Set(key, value, new MemoryCacheEntryOptions().AddExpirationToken(changeToken); 案例2:配置文件统一读取类,当配置文件修改时,利用ChangeToken重新读取配置 public class ConfigSetting { private IConfiguration _configuration = null; public ConfigSetting(IConfiguration configuration) { _configuration = configuration; ChangeToken.OnChange(()=>configuration.GetReloadToken(), () => { reloadConfiguration(); }); reloadConfiguration(); } private void reloadConfiguration() { _siteName = _configuration["SiteName"]; _siteID = _configuration.GetValue<int>("SiteID"); } private string _siteName = null; private int _siteID = 0; public string SiteName { get{return _siteName; } } public int SiteID { get{return _siteID; } } } 参考: https://www.cnblogs.com/artech/p/inside-asp-net-core-04-02.html https://www.cnblogs.com/uoyo/p/12509871.html https://www.cnblogs.com/jionsoft/p/12249326.html
在“物理文件”这个圈子里面,IChangeToken的真身叫做PollingFileChangeToken 在“配置系统”这个圈子里面,IChangeToken的真身叫做ConfigurationReloadToken 如果不需要IChangeToken可以使用NullChangeToken.Singleton
PollingFileChangeToken
ConfigurationReloadToken
NullChangeToken.Singleton
CancellationChangeToken是一个用的比较多的实现类,它包含一个CancellationToken属性,这个属性是通过构造函数来初始化的,简化代码如下:
public class CancellationChangeToken : IChangeToken { public CancellationChangeToken(CancellationToken cancellationToken) { Token = cancellationToken; } public bool ActiveChangeCallbacks { get; private set; } = true; public bool HasChanged => Token.IsCancellationRequested; private CancellationToken Token { get; } public IDisposable RegisterChangeCallback(Action<object> callback, object state) { return Token.Register(callback, state); } }
CancellationChangeToken只是对CancellationToken的包装。那为啥不直接让CancellationToken实现IChangeToken呢?我感觉是设计意图不同,CancellationToken设计时主要是考虑应用在取消异步操作这件事上的,只是碰巧取消异步操作与更改通知设计思路是相似的,所以才出现CancellationChangeToken
CompositeChangeToken代表由多个IChangeToken组合而成的复合型IChangeToken对象。如下面的代码片段所示,我们在调用构造函数创建一个CompositeChangeToken对象的时候,需要提供这些IChangeToken对象。对于一个CompositeChangeToken对象来说,只要组成它的任何一个IChangeToken发生改变,其HasChanged属性将会变成True,而注册的回调自然会被执行。至于ActiveChangeCallbacks属性,只要任何一个IChangeToken的同名属性返回True,该属性就会返回True。
public class CompositeChangeToken : IChangeToken { public bool ActiveChangeCallbacks { get; } public IReadOnlyList<IChangeToken> ChangeTokens { get; } public bool HasChanged { get; } public CompositeChangeToken(IReadOnlyList<IChangeToken> changeTokens); public IDisposable RegisterChangeCallback(Action<object> callback, object state); }
static void Main(string[] args) { A a = new A(); IChangeToken changeToken = a.CreateToken(); B b = new B(changeToken); C c = new C(changeToken); Console.ReadKey(); }
public class A { CancellationTokenSource _tokenSource = null; public A() { Task.Run(() => { while (true) { Task.Delay(1000).Wait(); Console.WriteLine("A状态已修改"); this.ChangeState(); } }); } public void ChangeState() { _tokenSource.Cancel(); } public IChangeToken CreateToken() { _tokenSource = new CancellationTokenSource(); return new CancellationChangeToken(_tokenSource.Token); } } public class B { public B(IChangeToken changeToken) { changeToken.RegisterChangeCallback((state) => { Console.WriteLine("B收到通知"); }, null); } } public class C { public C(IChangeToken changeToken) { changeToken.RegisterChangeCallback((state) => { Console.WriteLine("C收到通知"); }, null); } }
A轮询修改状态,但是B、C只会调用一次。需要借助ChangeToken,可以永远监控A状态
static void Main(string[] args) { A a = new A(); ChangeToken.OnChange(() => a.CreateToken(), () => { Console.WriteLine("监听到A状态已修改"); }); Console.ReadKey(); }
搞懂了IChangeToken我们就很轻松就能理解了ChangeToken,作为静态类的它,肯定是作为一个工具类的实现。
ChangeToken.OnChange( () => physicalFileProvider.Watch("*.*"), () => Console.WriteLine("检测到文件夹有变化!") );
那么您可能会说,我直接使用pysicalFileProvider.Watch()方法返回的IChangeToken的RegisterChangeCallback方法订阅不行吗?他们有什么区别吗? 答案是:“调用次数”。使用RegisterChangeCallback的方法,只会执行一次回调内容,因为当“令牌”用了一次之后,其实它就失效了。所以上面那个监控文件改动的代码,当第二次文件改动的时候,它其实是不会再执行回调的。 而使用ChangeToken这个静态类,它就可以帮助您不断的去获取新“令牌”然后注册对应的回调,所以就能够保证咱们多次改变也能触发回调了。 () => physicalFileProvider.Watch("*.*")这部分代码我们可以称它为“令牌生产过程”,而() => Console.WriteLine("检测到文件夹有变化!")叫做“令牌的消费过程”。 ChangeToken 干的事情就是:当消费者消费之后,就又会去让“生产过程”再生成一个令牌出来,并且在该令牌上挂载“消费过程”,这样就能保证能够一直“观察”下去了。
() => physicalFileProvider.Watch("*.*")
() => Console.WriteLine("检测到文件夹有变化!")
public static void Main(string[] args) { string dir = Directory.GetCurrentDirectory(); string fileName = Path.Combine(dir, "a.txt"); if (!File.Exists(fileName)) { var fs = File.Create(fileName); fs.Dispose(); } PhysicalFileProvider fileProvider = new PhysicalFileProvider(dir); IChangeToken changeToken = fileProvider.Watch("*.*"); //只会执行一次 changeToken.RegisterChangeCallback((state) => { Console.WriteLine("1文件被修改"); },"fan"); //每次修改都会执行(正确方式) ChangeToken.OnChange(()=>fileProvider.Watch("*.*"), () => { Console.WriteLine("2文件被修改"); }); Console.ReadKey(); }
案例1:添加缓存时提供IChangeToken,当依赖文件修改时,删除缓存
var fileProvider = new PhysicalFileProvider(Path.GetDirectoryName(dependencyFile)); var changeToken = fileProvider.Watch(Path.GetFileName(dependencyFile)); cache.Set(key, value, new MemoryCacheEntryOptions().AddExpirationToken(changeToken);
案例2:配置文件统一读取类,当配置文件修改时,利用ChangeToken重新读取配置
public class ConfigSetting { private IConfiguration _configuration = null; public ConfigSetting(IConfiguration configuration) { _configuration = configuration; ChangeToken.OnChange(()=>configuration.GetReloadToken(), () => { reloadConfiguration(); }); reloadConfiguration(); } private void reloadConfiguration() { _siteName = _configuration["SiteName"]; _siteID = _configuration.GetValue<int>("SiteID"); } private string _siteName = null; private int _siteID = 0; public string SiteName { get{return _siteName; } } public int SiteID { get{return _siteID; } } }
参考: https://www.cnblogs.com/artech/p/inside-asp-net-core-04-02.html https://www.cnblogs.com/uoyo/p/12509871.html https://www.cnblogs.com/jionsoft/p/12249326.html