聊一聊 .NET 中的 CompositeChangeToken

📅 2026/7/28 7:00:38
聊一聊 .NET 中的 CompositeChangeToken
聊一聊 .NET 中的 CompositeChangeToken在 .NET 应用程序开发中经常需要监控文件、配置或数据源的变更并做出响应。CancellationToken是处理异步操作取消的常用工具但单个令牌有时无法满足复杂场景的需求。例如你可能需要同时监控多个配置文件的变化或者当任何一项资源发生变更时触发某些操作。这时CompositeChangeToken就派上了用场。它是 .NET 在Microsoft.Extensions.Primitives命名空间中提供的一个强大工具用于组合多个IChangeToken实例形成统一的变更通知机制。本文将深入剖析CompositeChangeToken的原理并通过可运行的代码示例展示其实际应用场景帮助你理解如何在项目中使用它来简化变更监控逻辑。## CompositeChangeToken 的底层原理CompositeChangeToken是IChangeToken接口的一个实现它的核心思想是将多个IChangeToken称为子令牌组合成一个令牌。当任意一个子令牌报告变更时CompositeChangeToken就会触发变更通知。它的内部实现是通过订阅所有子令牌的RegisterChangeCallback方法并在任何一个子令牌变更时调用组合令牌的回调。从源码角度看可参考 .NET 开源仓库CompositeChangeToken维护一个IReadOnlyListIChangeToken集合并重写了HasChanged和ActiveChangeCallbacks属性。HasChanged会检查是否有任何一个子令牌报告了变更而ActiveChangeCallbacks则取决于是否有子令牌支持回调机制。当调用RegisterChangeCallback时它会为每个子令牌注册回调并创建一个复合的CancellationTokenSource以便在任意子令牌触发时取消当前令牌。这种设计模式类似于“或”逻辑只要有一个子令牌发生变化整个复合令牌就认为发生了变更。这比手动管理多个回调要简洁得多也避免了重复代码。## 典型应用场景CompositeChangeToken常用于以下场景-配置热更新监控多个配置文件如appsettings.json、custom.json的变化当任意文件修改时重新加载配置。-资源依赖链监控文件系统变化和数据库变更触发缓存刷新。-组合监控在微服务中监控多个外部服务的状态变化。让我们通过两个具体示例来理解它的用法。## 示例一监控多个文件变化假设你有一个应用需要同时监控config1.json和config2.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); }}// 简单的轮询文件变化令牌用于演示实际可用 FileSystemWatcherpublic 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(Actionobject 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 Liststring _configFiles new Liststring(); public void AddConfigFile(string path) { _configFiles.Add(path); } public IChangeToken CreateCompositeChangeToken() { var tokens new ListIChangeToken(); 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(Actionobject 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.json或custom.json控制台会立即输出“配置已变更重新加载配置…”。## 注意事项与最佳实践-线程安全CompositeChangeToken本身不是线程安全的但它的子令牌可能在不同线程上触发。在回调中处理共享资源时需加锁。-资源清理注册回调返回的IDisposable应妥善释放避免内存泄漏。-性能考量如果子令牌数量过多注册回调的开销会线性增长。建议将相关令牌分组而非使用一个巨大的组合。## 总结CompositeChangeToken是 .NET 中一个优雅的设计模式实现它将多个变更令牌组合成一个逻辑单元简化了“任意资源变化时触发操作”的场景。通过深入理解其原理基于“或”逻辑的订阅机制并结合实际代码示例你可以在配置管理、文件监控、缓存刷新等领域灵活运用它。相比手动管理多个回调CompositeChangeToken提供了更清晰、更可维护的代码结构是 .NET 开发中值得掌握的工具。希望本文能帮助你更好地理解和应用这一特性。