filesystemwatcher 是需主动防抖、兜底重连、线程隔离的“守卫员”,非开箱即用监听器;notifyfilter 至少设 lastwrite|filename|directoryname,changed 事件需防抖+临时文件过滤,ui 更新须跨线程调度,须监控 error 事件并实现心跳重建。

FileSystemWatcher 不是开箱即用的“监听器”,而是需要主动防抖、兜底重连、线程隔离的“守卫员”——直接注册事件就上线,90% 的生产故障都源于此。
NotifyFilter 设置不全导致事件漏触发
默认只监听 LastWrite,但文件重命名、目录移动、属性修改等操作根本不会触发 Changed 或 Created 事件。比如用户剪切粘贴一个文件到子目录,DirectoryName 和 FileName 没包含在 NotifyFilter 里,整个事件就静默丢失。
- 必须显式组合:至少包含
NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName - 如果还要捕获权限或时间戳变更,加上
NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.LastAccess - 避免滥用全量组合(如
NotifyFilters.All),它会显著增加内核通知负载,尤其在大目录下容易触发系统限流
Changed 事件频繁触发且含半截文件
编辑器保存文件时,常先写临时文件再原子替换,或分块写入未完成的大文件。此时 Changed 事件可能在文件仍被占用、内容不完整时就到达。
- 在事件处理器中加
try/catch (IOException),跳过正被其他进程锁定的文件 - 对
.tmp、.part、~$等常见临时后缀做前缀过滤:e.Name.EndsWith(".tmp") - 引入防抖:记录上次事件时间,
DateTime.Now - _lastEventTime 则忽略
后台线程直接操作 UI 导致崩溃或假死
FileSystemWatcher 所有事件都在非 UI 线程触发,WinForms 中直接更新 TextBox.Text 或 WPF 中修改 ItemsSource 会抛 InvalidOperationException;即使没崩溃,也可能因跨线程访问引发状态错乱。
- WinForms 下用
Control.Invoke包裹 UI 更新逻辑:if (InvokeRequired) { Invoke(...); return; } - WPF 下用
Dispatcher.Invoke,别依赖CheckAccess()后再调用——它和实际执行之间存在竞态 - 更稳妥的做法是把事件数据先塞进线程安全队列(如
ConcurrentQueue<filesystemeventargs></filesystemeventargs>),由 UI 线程定时消费
监控意外中断却无感知
网络共享路径断连、目录权限被回收、磁盘脱机、甚至 Windows 服务暂停,都会让 FileSystemWatcher 静默停止工作——EnableRaisingEvents 仍为 true,但事件再也不会来。
- 必须订阅
Error事件,并在回调里检查e.GetException()类型,常见如UnauthorizedAccessException、DirectoryNotFoundException - 实现心跳机制:每 30 秒尝试读取监控路径下的某个固定文件(如
watcher.Path + "\_heartbeat"),失败则手动重建FileSystemWatcher实例 - 不要复用旧实例:重建时先设
watcher.EnableRaisingEvents = false,再Dispose(),最后 new 一个新对象
最易被忽略的点是:FileSystemWatcher 的可靠性不取决于你注册了多少事件,而取决于你是否把它当成一个会死、会卡、会骗你的“有状态服务”来对待——所有兜底逻辑都得自己写,框架不会替你扛。











