會寫這篇純屬機緣巧合,雖然一直以來認為對單一檔案的讀、寫操作是不衝突,可並行的,但實際並未實踐過。正好有個UWP的程式要並行讀取由Desktop Extension建立的文字,需要有個原型程式來驗證,那不妨點開最新的VS 2022,順手試試新的語法糖。
首先我們明確本篇對檔案的操作均通過FileStream類來實現,FileStream在.NET 6進行了完全的重寫,提高了效能和可靠性。但是本篇提到的共享讀寫許可權,在之前版本也是完全支援的。
本篇提到的同時讀寫功能依賴FileStream的這個建構函式:
public FileStream (string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share);
接下來我們通過實際的程式碼來進行分析。建立第一個工程CreateWriteSharedFile,該工程為.NET 6的Console程式,用於新建和寫入內容到名為TestFile.txt的檔案中。
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "TestFile.txt"); var fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite); StreamWriter sw = new StreamWriter(fileStream); int cout = 0; while (true) { for (var i = 0; i < 10; i++) { sw.WriteLine(cout++); Console.WriteLine(cout); } sw.Flush(); await Task.Delay(1000); }
沒有名稱空間,沒有類名和Main函式,這是C# 10裡的新語法糖——頂級語句。作為簡化後的程式入口點,十分適合簡短的示例程式,對初學者也更友好。
程式碼的內容也很好懂,就是每隔1秒連續寫入10個自增的數字。唯一值得留意的是FileShare.ReadWrite,這個列舉標識對應的是後續其他對該檔案的請求,不管是該程式內還是另外程式,均給與ReadWrite的許可權。
我們的第二個工程ReadSharedFile僅做讀取的操作,所以上面CreateWriteSharedFile中的FileShare只給Read也可以。但是相反,ReadSharedFile因為要允許CreateWriteSharedFile來進行寫操作,所以它必須給與FileShare.Write列舉。
var path =Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "TestFile.txt"); var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read,FileShare.Write); var reader = new StreamReader(fileStream); while (!reader.EndOfStream) { Console.WriteLine(reader.ReadLine()); await Task.Delay(1000); }
上述程式碼是在ReadSharedFile工程中讀取由CreateWirteSharedFile建立的TestFile.txt中的內容。想要測試的話,build成功後執行對應的exe檔案即可。並行的讀和寫操作較為容易理解,也不會存在衝突或生成髒資料的問題。
但如果是同時進行寫操作會怎麼樣呢?之前的FileShare.ReadWrite就是為接下來的測試準備的。我們建立第二個寫檔案的工程SecondWriteSharedFile,同樣要注意除了設定Read以外,還要為CreateWriteSharedFile特別準備Write許可權,才能實現兩邊同時寫入該檔案的要求。
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "TestFile.txt"); var fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite); StreamWriter sw = new StreamWriter(fileStream); while (true) { for (var i = 0; i < 10; i++) { sw.WriteLine("A".PadRight(i,'A')); Console.WriteLine("A".PadRight(i, 'A')); } sw.Flush(); await Task.Delay(1000); }
非常不幸的是,SecondWriteSharedFile在預設情況下,同樣會從檔案的頭部開始寫入,這樣就覆蓋了先執行的CreateWriteSharedFile在同樣位置寫入的內容。所以在一般情況下,我們要避免並行的寫操作,這樣極容易互相覆蓋產生髒資料。
本篇簡單地討論了通過FileShare列舉,使用FileStream並行地讀寫檔案的一般場景。希望能夠拋磚引玉,給各位大佬在實際生產場景中以微小的幫助。
示例程式碼:(因為GitHub經常打不開,我在gitee也同樣放了一份)
https://github.com/manupstairs/FileReadWriteSample
https://gitee.com/manupstairs/FileReadWriteSample
以下連結,是MS Learn上Windows開發的入門課程,單個課程三十分鐘到60分鐘不等,想要補充基礎知識的同學點這裡: