如何在C#中向FileSystemWatcher监控路径写入文件并避免无限循环
解决C# FileSystemWatcher替换文件时的无限循环问题
这个场景我太熟悉了——FileSystemWatcher监控文件夹,当外部修改文件时用备份覆盖,但自己的写入又触发了Watcher事件,陷入循环死胡同。核心就是要区分程序自身的写入和外部的未授权修改,下面给你几个实用的解决方案:
方案一:临时禁用事件监听(最简单直接)
在执行文件替换操作前,先关闭FileSystemWatcher的事件触发,完成后再重新开启。注意要加锁保证线程安全,因为Watcher的事件是在后台线程触发的,防止多个事件同时执行导致状态混乱。
修改你的代码如下:
// 定义一个全局锁对象,保证线程安全 private readonly object _watcherLock = new object(); public FileSystemWatcher CreateAndExecute(string path) { Console.WriteLine("Watching " + path); FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(); fileSystemWatcher.Path = path; fileSystemWatcher.IncludeSubdirectories = false; fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; fileSystemWatcher.Filter = "*.txt"; fileSystemWatcher.Changed += OnChange; fileSystemWatcher.InternalBufferSize = 32768; fileSystemWatcher.EnableRaisingEvents = true; return fileSystemWatcher; // 注意这里要返回watcher对象,否则后续无法操作 } private void OnChange(object source, FileSystemEventArgs e) { lock (_watcherLock) { var watcher = (FileSystemWatcher)source; // 先禁用事件触发,避免自身写入触发循环 watcher.EnableRaisingEvents = false; try { // 从数据库读取备份副本并写入文件,替换成你的实际数据库读取逻辑 string backupContent = GetBackupContentFromDatabase(e.FullPath); File.WriteAllText(e.FullPath, backupContent); Console.WriteLine($"Restored file: {e.FullPath}"); } finally { // 无论操作成功与否,都要恢复事件监听 watcher.EnableRaisingEvents = true; } } }
优缺点:实现简单,但禁用监听期间如果有外部修改,Watcher会错过这些事件。如果你的场景对实时性要求不是极高,这个方案足够用。
方案二:用标记变量区分自身操作(平衡简单与可靠)
定义一个布尔标记,在执行恢复操作时设为true,事件触发时先检查这个标记,如果是自身操作就直接跳过。同样要加锁保证线程安全。
代码示例:
private bool _isRestoringFile = false; private readonly object _restoreLock = new object(); private void OnChange(object source, FileSystemEventArgs e) { lock (_restoreLock) { // 如果当前正在执行恢复操作,直接跳过事件处理 if (_isRestoringFile) { return; } _isRestoringFile = true; try { // 执行备份替换逻辑,替换成你的实际数据库读取逻辑 string backupContent = GetBackupContentFromDatabase(e.FullPath); File.WriteAllText(e.FullPath, backupContent); Console.WriteLine($"Restored file: {e.FullPath}"); } finally { // 重置标记,确保后续事件正常处理 _isRestoringFile = false; } } }
优缺点:不用禁用监听,不会错过外部事件,实现也不复杂,是大多数场景下的首选方案。
方案三:基于文件哈希判断(最可靠)
通过计算文件内容的哈希值,判断当前触发事件的文件是否是刚恢复的备份副本。如果哈希和备份一致,说明是自身写入,直接跳过。
代码示例:
// 记录每个文件的备份哈希,用于后续比对 private readonly Dictionary<string, string> _fileBackupHashes = new Dictionary<string, string>(); private readonly object _hashLock = new object(); private void OnChange(object source, FileSystemEventArgs e) { lock (_hashLock) { string currentFileHash = CalculateFileHash(e.FullPath); string backupContent = GetBackupContentFromDatabase(e.FullPath); string backupHash = CalculateStringHash(backupContent); // 如果当前文件哈希和备份哈希一致,说明是自身刚写入的,跳过 if (currentFileHash == backupHash) { return; } // 执行替换操作 File.WriteAllText(e.FullPath, backupContent); // 更新哈希记录 if (_fileBackupHashes.ContainsKey(e.FullPath)) { _fileBackupHashes[e.FullPath] = backupHash; } else { _fileBackupHashes.Add(e.FullPath, backupHash); } Console.WriteLine($"Restored file: {e.FullPath}"); } } // 计算文件的MD5哈希 private string CalculateFileHash(string filePath) { using (var md5 = MD5.Create()) { using (var stream = File.OpenRead(filePath)) { byte[] hashBytes = md5.ComputeHash(stream); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } } } // 计算字符串的MD5哈希 private string CalculateStringHash(string content) { using (var md5 = MD5.Create()) { byte[] contentBytes = Encoding.UTF8.GetBytes(content); byte[] hashBytes = md5.ComputeHash(contentBytes); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } }
优缺点:完全基于内容判断,不会因为线程问题出现误判,适合对准确性要求极高的场景,但需要额外的哈希计算,性能上略有损耗。
总结
如果你的场景对实时性要求一般,方案一足够简单;追求平衡的话方案二是首选;如果需要绝对可靠的判断,就用方案三。
内容的提问来源于stack exchange,提问作者Eddy




