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
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;
//後續程式碼
執行可以看見這樣的輸出,說明方案可行。
上面的方案似乎已經解決了我們的問題,我興致勃勃地部署到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下執行。
為了防止無限迴圈,設定了超時,如果在指定的時間內無法完成,那麼程式直接跳出。