System.IO.FileSystemWatcher的坑

  • System.IO.FileSystemWatcher的坑已关闭评论
  • 175 次浏览
  • A+
所属分类:.NET技术
摘要

System.IO命名空间下面有一个FileSystemWatcher,这个东西可以实现文件变动的提醒。需要监控文件夹变化(比如FTP服务器)的情形非常适用。

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下运行。

为了防止无限循环,设置了超时,如果在指定的时间内无法完成,那么程序直接跳出。

参考