FileSystemWatcher调用后重置EnableRaisingEvents及多目录文件监控类问题
看起来你已经搭好了Watcher类的基础框架,但在多目录监控和EnableRaisingEvents意外重置的问题上遇到了卡点——我来帮你优化这个实现,同时解决这两个核心问题。
优化后的多目录监控Watcher类
先上完整的改进代码,后面再拆解关键细节:
using System; using System.Collections.Generic; using System.IO; using System.Linq; public class Watcher { // 用集合存储多个监控实例,支持同时监控多目录 private readonly List<FileSystemWatcher> _watchers = new List<FileSystemWatcher>(); // 保留你定义的委托与事件 public delegate void FileCreatedEventHandler(object sender, FileSystemEventArgs e); public delegate void FileRenamedEventHandler(object sender, RenamedEventArgs e); public delegate void LogEventHandler(object sender, string e); public event FileCreatedEventHandler FileCreated; public event FileRenamedEventHandler FileRenamed; public event LogEventHandler Log; // 添加要监控的目录,支持可选监控子目录 public void AddWatchDirectory(string path, bool includeSubdirectories = false) { if (!Directory.Exists(path)) { OnLog($"警告:目录不存在,无法监控 -> {path}"); return; } var watcher = new FileSystemWatcher(path) { IncludeSubdirectories = includeSubdirectories, EnableRaisingEvents = true, // 只关注我们需要的事件类型,减少不必要的触发 NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName }; // 绑定核心事件 watcher.Created += OnFileCreated; watcher.Renamed += OnFileRenamed; // 重点:绑定Error事件,解决EnableRaisingEvents被意外禁用的问题 watcher.Error += OnWatcherError; _watchers.Add(watcher); OnLog($"已开始监控目录 -> {path}"); } // 移除指定目录的监控 public void RemoveWatchDirectory(string path) { var targetWatcher = _watchers.FirstOrDefault(w => w.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); if (targetWatcher != null) { targetWatcher.EnableRaisingEvents = false; targetWatcher.Dispose(); _watchers.Remove(targetWatcher); OnLog($"已停止监控目录 -> {path}"); } } // 停止所有监控并释放资源 public void StopAllWatches() { foreach (var watcher in _watchers) { watcher.EnableRaisingEvents = false; watcher.Dispose(); } _watchers.Clear(); OnLog("已停止所有目录监控"); } // 触发FileCreated事件 private void OnFileCreated(object sender, FileSystemEventArgs e) { FileCreated?.Invoke(sender, e); OnLog($"检测到文件创建 -> {e.FullPath}"); } // 触发FileRenamed事件 private void OnFileRenamed(object sender, RenamedEventArgs e) { FileRenamed?.Invoke(sender, e); OnLog($"检测到文件重命名 -> {e.OldFullPath} → {e.FullPath}"); } // 处理监控器错误,修复EnableRaisingEvents被重置的问题 private void OnWatcherError(object sender, ErrorEventArgs e) { var watcher = sender as FileSystemWatcher; if (watcher == null) return; var exception = e.GetException(); OnLog($"监控器错误[{watcher.Path}]:{exception.Message},尝试恢复监控..."); // 核心修复逻辑:当监控器因错误自动禁用事件触发时,重新启用 try { watcher.EnableRaisingEvents = false; watcher.EnableRaisingEvents = true; OnLog($"已恢复目录监控 -> {watcher.Path}"); } catch (Exception ex) { OnLog($"恢复监控失败[{watcher.Path}]:{ex.Message}"); } } // 统一触发日志事件 private void OnLog(string message) { Log?.Invoke(this, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}"); } }
关键改进细节
- 多目录支持:用
List<FileSystemWatcher>替代单个实例,通过AddWatchDirectory可以自由添加任意多个监控目录,满足多目录监控需求。 - 解决EnableRaisingEvents重置问题:
- FileSystemWatcher遇到错误(比如目录被删除、权限不足、网络目录断开)时,会自动把
EnableRaisingEvents设为false,我们通过绑定Error事件,在错误发生后尝试重新启用事件触发。 - 在添加目录时显式设置
EnableRaisingEvents = true,避免依赖默认值导致的意外问题。
- FileSystemWatcher遇到错误(比如目录被删除、权限不足、网络目录断开)时,会自动把
- 资源安全:提供
RemoveWatchDirectory和StopAllWatches方法,确保在停止监控时正确释放FileSystemWatcher资源,避免内存泄漏。 - 日志调试:统一的日志输出,方便你跟踪监控状态、错误信息和事件触发情况。
简单使用示例
public class Program { static void Main(string[] args) { var watcher = new Watcher(); // 订阅事件 watcher.FileCreated += (sender, e) => Console.WriteLine($"新文件:{e.FullPath}"); watcher.FileRenamed += (sender, e) => Console.WriteLine($"文件改名:{e.OldFullPath} → {e.FullPath}"); watcher.Log += (sender, msg) => Console.WriteLine(msg); // 添加两个监控目录 watcher.AddWatchDirectory(@"D:\Work\Dir1", includeSubdirectories: true); watcher.AddWatchDirectory(@"D:\Work\Dir2"); Console.WriteLine("按任意键停止监控..."); Console.ReadKey(); watcher.StopAllWatches(); } }
另外还有几个小提醒:
- 确保程序有足够的权限访问监控目录,否则会频繁触发错误。
- 如果在事件处理中对文件进行读写操作,可能会触发重复事件,必要时可以添加延迟处理或者简单的锁机制。
- 如果监控的目录被删除后又重建,当前实现不会自动重新监控,你可以在
OnWatcherError中添加目录存在性检查,自动重新添加监控逻辑。
内容的提问来源于stack exchange,提问作者Paul S Chapman
相关产品推荐
相关产品推荐

