聊一聊 .NET 中的 CompositeChangeToken

聊一聊 .NET 中的 CompositeChangeToken

在 .NET 应用程序开发中,经常需要监控文件、配置或数据源的变更,并做出响应。CancellationToken 是处理异步操作取消的常用工具,但单个令牌有时无法满足复杂场景的需求。例如,你可能需要同时监控多个配置文件的变化,或者当任何一项资源发生变更时触发某些操作。这时,CompositeChangeToken 就派上了用场。它是 .NET 在 Microsoft.Extensions.Primitives 命名空间中提供的一个强大工具,用于组合多个 IChangeToken 实例,形成统一的变更通知机制。本文将深入剖析 CompositeChangeToken 的原理,并通过可运行的代码示例展示其实际应用场景,帮助你理解如何在项目中使用它来简化变更监控逻辑。## CompositeChangeToken 的底层原理CompositeChangeTokenIChangeToken 接口的一个实现,它的核心思想是将多个 IChangeToken (称为子令牌)组合成一个令牌。当任意一个子令牌报告变更时,CompositeChangeToken 就会触发变更通知。它的内部实现是通过订阅所有子令牌的 RegisterChangeCallback 方法,并在任何一个子令牌变更时调用组合令牌的回调。从源码角度看(可参考 .NET 开源仓库),CompositeChangeToken 维护一个 IReadOnlyList<IChangeToken> 集合,并重写了 HasChangedActiveChangeCallbacks 属性。HasChanged 会检查是否有任何一个子令牌报告了变更,而 ActiveChangeCallbacks 则取决于是否有子令牌支持回调机制。当调用 RegisterChangeCallback 时,它会为每个子令牌注册回调,并创建一个复合的 CancellationTokenSource,以便在任意子令牌触发时取消当前令牌。这种设计模式类似于"或"逻辑:只要有一个子令牌发生变化,整个复合令牌就认为发生了变更。这比手动管理多个回调要简洁得多,也避免了重复代码。## 典型应用场景CompositeChangeToken 常用于以下场景:- 配置热更新 :监控多个配置文件(如 appsettings.jsoncustom.json)的变化,当任意文件修改时重新加载配置。- 资源依赖链 :监控文件系统变化和数据库变更,触发缓存刷新。- 组合监控 :在微服务中监控多个外部服务的状态变化。让我们通过两个具体示例来理解它的用法。## 示例一:监控多个文件变化假设你有一个应用,需要同时监控 config1.jsonconfig2.json 的变化。当任意文件被修改时,输出一条日志。下面的代码展示了如何使用 CompositeChangeToken 实现。csharpusing Microsoft.Extensions.Primitives;using System;using System.IO;using System.Threading;using System.Threading.Tasks;class Program{ static async Task Main(string[] args) { // 创建两个文件变化令牌 var file1 = "config1.json"; var file2 = "config2.json"; // 确保文件存在(这里简化处理,实际应检查) File.WriteAllText(file1, "{}"); File.WriteAllText(file2, "{}"); // 创建 IChangeToken 实例 var token1 = new PollingFileChangeToken(file1); var token2 = new PollingFileChangeToken(file2); // 组合令牌 var compositeToken = new CompositeChangeToken(new[] { token1, token2 }); // 注册回调 using (var cancellationTokenSource = new CancellationTokenSource()) { compositeToken.RegisterChangeCallback(state => { Console.WriteLine("检测到文件变化!"); // 取消主循环,避免重复触发 cancellationTokenSource.Cancel(); }, null); try { // 等待文件变化 Console.WriteLine("等待配置文件变化... 修改文件以测试。"); await Task.Delay(Timeout.Infinite, cancellationTokenSource.Token); } catch (OperationCanceledException) { Console.WriteLine("检测完成。"); } } // 清理 File.Delete(file1); File.Delete(file2); }}// 简单的轮询文件变化令牌(用于演示,实际可用 FileSystemWatcher)public class PollingFileChangeToken : IChangeToken{ private readonly string _filePath; private DateTime _lastWriteTime; public PollingFileChangeToken(string filePath) { _filePath = filePath; _lastWriteTime = File.GetLastWriteTimeUtc(filePath); } public bool HasChanged { get { var currentTime = File.GetLastWriteTimeUtc(_filePath); return currentTime != _lastWriteTime; } } public bool ActiveChangeCallbacks => true; public IDisposable RegisterChangeCallback(Action<object> callback, object state) { // 简化实现:每100ms检查一次 var timer = new Timer(_ => { if (HasChanged) { callback(state); } }, null, 100, 100); return timer; }}代码解释 :- 我们创建了两个 PollingFileChangeToken(自定义轮询令牌),分别监控两个文件。- CompositeChangeToken 将它们组合起来,注册的回调会在任意文件变化时触发。- 使用 CancellationTokenSource 管理程序生命周期,避免无限等待。运行此代码后,修改任意一个文件,控制台会输出"检测到文件变化!"。## 示例二:在配置热更新中使用 CompositeChangeToken在实际的 ASP.NET Core 应用中,CompositeChangeToken 常用于配置系统。例如,你可能需要同时监控默认配置文件和自定义配置文件。下面的示例模拟了这一过程。csharpusing Microsoft.Extensions.Primitives;using System;using System.Collections.Generic;using System.IO;using System.Threading;class ConfigurationMonitor{ private readonly List<string> _configFiles = new List<string>(); public void AddConfigFile(string path) { _configFiles.Add(path); } public IChangeToken CreateCompositeChangeToken() { var tokens = new List<IChangeToken>(); foreach (var file in _configFiles) { // 使用 FileSystemWatcher 实现实时监控 tokens.Add(new FileWatcherChangeToken(file)); } return new CompositeChangeToken(tokens); }}// 基于 FileSystemWatcher 的令牌public class FileWatcherChangeToken : IChangeToken, IDisposable{ private readonly FileSystemWatcher _watcher; private bool _hasChanged; public FileWatcherChangeToken(string filePath) { var directory = Path.GetDirectoryName(filePath); var fileName = Path.GetFileName(filePath); _watcher = new FileSystemWatcher(directory, fileName) { NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime }; _watcher.Changed += (s, e) => { _hasChanged = true; }; _watcher.EnableRaisingEvents = true; } public bool HasChanged => _hasChanged; public bool ActiveChangeCallbacks => true; public IDisposable RegisterChangeCallback(Action<object> callback, object state) { _watcher.Changed += (s, e) => { callback(state); // 重置标志,避免重复触发 _hasChanged = false; }; // 返回一个空 IDisposable(简化实现) return new DisposableAction(() => { }); } public void Dispose() { _watcher?.Dispose(); }}// 辅助类public class DisposableAction : IDisposable{ private readonly Action _action; public DisposableAction(Action action) => _action = action; public void Dispose() => _action();}// 主程序class Program{ static void Main(string[] args) { var monitor = new ConfigurationMonitor(); monitor.AddConfigFile("appsettings.json"); monitor.AddConfigFile("custom.json"); // 创建文件 File.WriteAllText("appsettings.json", "{}"); File.WriteAllText("custom.json", "{}"); var compositeToken = monitor.CreateCompositeChangeToken(); Console.WriteLine("监控配置变化... 修改任意文件以测试。"); compositeToken.RegisterChangeCallback(state => { Console.WriteLine($"配置已变更,重新加载配置..."); // 这里可以添加配置重载逻辑 }, null); Console.ReadLine(); // 保持程序运行 // 清理 File.Delete("appsettings.json"); File.Delete("custom.json"); }}代码解释 :- FileWatcherChangeToken 使用 FileSystemWatcher 实现实时监控,比轮询更高效。- ConfigurationMonitor 类封装了配置文件的添加和组合令牌的创建。- 当任意文件被修改时,回调触发,模拟配置热更新。运行后,修改 appsettings.jsoncustom.json,控制台会立即输出"配置已变更,重新加载配置..."。## 注意事项与最佳实践- 线程安全CompositeChangeToken 本身不是线程安全的,但它的子令牌可能在不同线程上触发。在回调中处理共享资源时需加锁。- 资源清理 :注册回调返回的 IDisposable 应妥善释放,避免内存泄漏。- 性能考量 :如果子令牌数量过多,注册回调的开销会线性增长。建议将相关令牌分组,而非使用一个巨大的组合。## 总结CompositeChangeToken 是 .NET 中一个优雅的设计模式实现,它将多个变更令牌组合成一个逻辑单元,简化了"任意资源变化时触发操作"的场景。通过深入理解其原理(基于"或"逻辑的订阅机制),并结合实际代码示例,你可以在配置管理、文件监控、缓存刷新等领域灵活运用它。相比手动管理多个回调,CompositeChangeToken 提供了更清晰、更可维护的代码结构,是 .NET 开发中值得掌握的工具。希望本文能帮助你更好地理解和应用这一特性。

相关推荐
界面开发小八哥3 小时前
界面控件DevExpress Blazor v26.1新版亮点 - 辅助功能增强
.net·界面控件·blazor·devexpress·ui开发
比卡超哥6 小时前
记一次 .NET 某光谱检测软件 内存暴涨分析
.net
驱动小百科8 小时前
Windows运行库合集下载 VC++、DirectX、.NET运行库安装教程
c++·windows·.net·windows运行库合集下载·windows运行库安装
你有哈莫吗10 小时前
记一次 .NET 某自动化智能制造软件 卡死分析
自动化·.net·制造
水瓶夜之12 小时前
.NET 中的新增功能系列文章——.NET SDK中的新增功能
前端·chrome·.net
布鲁飞丝12 小时前
对 .NET线程 异常退出引发程序崩溃的反思
数据库·c#·.net
geovindu12 小时前
CSharp: LogHelper
开发语言·后端·c#·.net
何解忧115 小时前
.NET 中的新增功能系列文章—— .NET MAUI 中的新增功能
.net
仙宇觉尘1 天前
一款基于 .NET 开源美观、功能丰富的串口调试工具
开源·.net