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等需要一段時間才能完成傳輸的情況下,直接在時間處理程序中處理文件會由于文件不完整導致錯誤 ??上У氖牵?code>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下運行 。
為了防止無限循環,設置了超時,如果在指定的時間內無法完成,那么程序直接跳出 。
參考
  • https://stackoverflow.com/questions/4277991/c-sharp-filesystemwatcher-how-to-know-file-copied-completely-into-the-watch-fol
【System.IO.FileSystemWatcher的坑】

    推薦閱讀