System.IO.FileSystemWatcher的坑

System.IO命名空间下面有一个FileSystemWatcher , 这个东西可以实现文件变动的提醒 。需要监控文件夹变化(比如FTP服务器)的情形非常适用 。
需要监控文件新建时 , 我们可以这么写:
_fileSystemWatcher.Path = path;_fileSystemWatcher.IncludeSubdirectories = true;_fileSystemWatcher.Created += _fileSystemWatcher_Created;_fileSystemWatcher.EnableRaisingEvents = true;protected async void _fileSystemWatcher_Created(object sender, FileSystemEventArgs e){Console.WriteLine(e.FullPath);}感觉还是挺方便的吧?接下来就是坑了 。
传输延迟问题FileSystemWatcher只要发现文件创建就触发了 , 大文件或者FTP等需要一段时间才能完成传输的情况下 , 直接在时间处理程序中处理文件会由于文件不完整导致错误 。可惜的是 , FileSystemWatcher并没有內建任何机制可以保障文件传输完成再触发Created事件 , 我们只能靠自己代码保障 。

以下代码运行于.NET 6 , Windows 11 , Rocky Linux 9
Windows only方案
  • FileSystemWatcher除了Created , 还提供了Changed事件 , 我们可以先监听Created事件 , 然后再监控Changed的情况 , 当文件属性不在变化时 , 认为是传输完毕了 。这种方案可行 , 不过感觉有点太麻烦了 , 我需要监听两个事件 , 还需要处理先后顺序 , 其实我只想知道创建而已...
  • 在Created事件中 , 使用排他性的文件打开操作在File.Open()函数中 , 有重载可以提供独占的访问 , 访问不成功 , 文件会弹出错误 。
//防止文件上传时间过长 , 导致无法正常识别if (!File.Exists(e.FullPath)) return;var accessable = false;for (int i = 0; i < 5; i++){try{using (File.Open(e.FullPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)){Console.WriteLine("Break");accessable = true;break;}}catch (Exception){Console.WriteLine("Loop" + i);}await Task.Delay(3000);}//文件超时无法读取 , 失败 。if (!accessable) return;//后续代码运行可以看见这样的输出 , 说明方案可行 。
System.IO.FileSystemWatcher的坑

文章插图
Linux与Windows通用方案上面的方案似乎已经解决了我们的问题 , 我兴致勃勃地部署到Linux机器上时却死活无法正常工作 , Debug发现Open()这个方法居然可以一次直接通过 , 看来Linux下的Share不能正常独占这个文件 , 还得换一个方法 。
protected async void _fileSystemWatcher_Created(object sender, FileSystemEventArgs e){//防止文件上传时间过长 , 导致无法正常识别if (!File.Exists(e.FullPath)) return;var accessable = false;for (int i = 0; i < 5; i++){await Task.Delay(3000);Console.WriteLine("loop" + i);var time1 = File.GetLastWriteTimeUtc(e.FullPath);await Task.Delay(1000);var time2 = File.GetLastWriteTimeUtc(e.FullPath);if (time1 == time2){accessable = true;break;}}//文件超时无法读取 , 失败 。if (!accessable) return;//后续代码}我们可以在程序中定时检查文件的最后修改时间 , 如果相隔一段时间的两次最后修改时间一致的话 , 那说明文件已经完成了传输 , 这种方式不依赖于打开操作 , 并且可以在Windows和Linux下运行 。
为了防止无限循环 , 设置了超时 , 如果在指定的时间内无法完成 , 那么程序直接跳出 。
参考

经验总结扩展阅读