netcore中的IChangeToken

3/26/2024 1:54:49 PM
256
0

看看这个接口长什么样子

namespace Microsoft.Extensions.Primitives
{
    public interface IChangeToken
    {
        bool HasChanged { get; }
        bool ActiveChangeCallbacks { get; }
        IDisposable RegisterChangeCallback(Action<object> callback, object state);
    }
}

IChangeToken从字面理解就是某种跟变化相关的令牌。其实它可以在检测到数据发生变化时发送一个通知,如果数据发生变化,则HasChanged属性就会变成true。ActiveChangeCallbacks这个属性用于表示当数据发生变化时,是否主动执行注册的回调函数,通常这个值设置为true。如果设置为false,则不会调用回调。

RegisterChangeCallback注册一个当数据发生变化时,自动执行的回调,它返回值为IDisposable对象,可以使用其Dispose方法解除掉注册的回调函数。

也许大家会好奇这个IChangeToken似乎很少见到,其实不然,IChangeToken在.net core的世界中可谓是无处不在,只不过以xxxChangeToken这种命名的方式存在,换句话说,当你看到一个类名为xxxChangeToken,那么大概率是实现了IChangeToken这个接口。

比如在文件系统中的PollingFileChangeToken,配置系统中的ConfigurationReloadToken,如下图

下面我使用监控文件变化,来演示一下IChangeToken的运行过程。

using (var fileProvider=new PhysicalFileProvider("d:\\test"))
{
	IChangeToken changeToken = fileProvider.Watch("test.txt");
	changeToken.RegisterChangeCallback(_ =>
	{
		Logger.ToConsole("file changed!",ConsoleColor.DarkGreen);
	},"");
	for (int i = 0; i < 3; i++)
	{
		Logger.ToConsole("add text to file", ConsoleColor.DarkGray);
		await File.AppendAllTextAsync(@"d:\test\test.txt", DateTime.Now.ToString());
		await Task.Delay(2000);
	}
}

PhysicalFileProvider类中的Watch方法在文件变化时会返回一个IChangeToken,然后注册一个回调函数放入RegisterChangeCallback中。这时我们每隔一段时间修改一下文本,模拟文件修改操作,效果如下。

可以看到,我们修改了三次文件,但是回调函数只执行了一次,说明这个“令牌”具有“一次性”的特征,也就是说,使用了一次,也就失效了。

如果想每次更改就触发回调怎么办呢,这就引出今儿的另一个主角,ChangeToken。

别看ChangeToken和IChangeToken就差了一个“I”,但其实ChangeToken并非实现了IChangeToken接口。ChangeToken是一个静态类,对外仅暴露OnChange函数。我们来看看这个函数长什么模样

OnChange函数接收一个返回值为IChangeToken的“producer”以及一个当令牌发生改变时,自从执行的回调函数“consumer”。我们按照这个对上面程序改造一下

ChangeToken.OnChange(
	() => fileProvider.Watch("test.txt"),
	() => Logger.ToConsole("file changed!", ConsoleColor.DarkGreen)
);

我们看到每次文件发生变化,都会自动执行塞进去的回调函数。

ChangeToken的OnChange函数在.net core中还是蛮常见的,最典型的应用恐怕就是大家熟知的配置文件更改时,自动加载配置项吧,大家如果感兴趣可以在源代码中查看FileConfigurationProvider一窥端倪。

这里再额外啰嗦两句,也许您会好奇OnChange函数是如何实现这种无限监控变化并执行的呢?

其实原理蛮简单的,简单来说,它利用您传入的producer获取IChangeToken,并注册IChangeToken的RegisterChangeCallback函数,在这个函数内部,先获取新的token,然后执行传入的consumer函数,最后利用新的token再来一遍,以此反复。我画了一幅草图帮助大家理解,仅仅是抛砖引玉,感兴趣的小伙伴可以再深入研究。

聊到这里IChangeToken和ChangeToken已经算是接近尾声了,也许您会好奇如何自己实现一个xxChangeToken,其实很简单,只要借助CancellationTokenSource这个类,您也可以轻松实现,事实上在.net core中大多数的xxChangeToken都是借助它来实现的。如下

public class ConfigurationReloadToken : IChangeToken
{
  private readonly CancellationTokenSource _cts = new CancellationTokenSource();
  ……
}
 

我在示例代码中,自定义了一个IChangeToken的实现,并展示数据发生变化时,如何响应,算是简单模拟了一下.net core配置文件变化时,是如何重新加载配置数据的,鉴于时间和篇幅,我就不在此展开了,感兴趣的小伙伴可以去一探究竟。

 

在最后,也许对于这种状态改变执行回调这样的操作,大家更熟悉的是使用事件订阅的方式,假如B关心A的变化,那么当A发生变化时,可以触发事件,B去订阅事件。这么做是完全可行的,但存在的最大的问题就是B需要紧密的耦合A,也就是B需要引用A。但往往作为B来说,仅仅是关心A的变化而已。这时候使用ChangeToken其实就是当做一个靠谱的中间层,A只要告诉它它发生了改变,而B只需要告诉它A变化时,我要做什么就ok了。

本文转自:https://zhuanlan.zhihu.com/p/344370027

全部评论



提问