.NET 小技巧 - 讀取其他程式開啟中的檔案
| | 2 | | ![]() |
寫了個小工具偷看網站即時 Log,配合 NLog 使用時正常,但遇到 log4net Log 會出現「The process cannot access the file 'xxxx.log' because it is being used by another process.」錯誤。(註:手邊系統大多以 NLog 為主,但部分老系統仍在用 log4net。.NET Log 程式庫評比)
小工具讀檔原本用 File.ReadAllBytes()
,推測讀取時需鎖定檔案避免其他程式繼續寫入,讀到不完整資料;研判 log4net 的行為與 NLog 不同,log4net 會長期保持檔案開啟,造成其他程序無法使用一般方式讀取。
解決方式不難,使用包含 FileShare 參數的 FileStream 建構式,開啟時宣告允許其他程序讀寫可避開問題。但要注意,這招跟 SQL 的 WITH (NOLOCK) 提示一樣,有可能讀到的是只寫一半的無效資料。小工具目的為偷窺,使用者已有心理準備,故能接受此副作用。
用兩支程式重現問題與驗證:
file-locker/Program.cs 開啟檔案,每秒寫入一行內容,按任意鍵結束:
var filePath = "D:\\lab\\target.txt";
using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
using (var sw = new StreamWriter(fs))
{
Console.WriteLine("File opened. Press any key to exit.");
while (true)
{
if (Console.KeyAvailable) break;
var timestamp = DateTime.Now.ToString("mm:ss.fff");
sw.WriteLine(timestamp);
sw.Flush();
Console.WriteLine(timestamp);
Thread.Sleep(1000);
}
Console.ReadKey(true); // read and don't show the key pressed
Console.WriteLine("Done. File closed.");
}
}
file-reader/Program.cs 使用 File.ReadAllBytes() 讀取 file-locker 寫入的檔案。
var filePath = "D:\\lab\\target.txt";
try {
var content = File.ReadAllBytes(filePath);
Console.WriteLine($"File readed, {content.Length} bytes.");
}
catch (Exception ex) {
Console.WriteLine("*** Error ***");
Console.WriteLine(ex.Message);
}
二者同時執行,重現前面所提檔案正被其他程式使用的錯誤。
修改程式,改用 FileStream,指定 FileShare.ReadWrite (允許後序開啟檔案進行讀取或寫入。 如果未指定,任何要開啟檔案以進行讀取或寫入的要求 (由這個處理序或其他處理序) 將會失敗。參考)
var filePath = "D:\\lab\\target.txt";
try {
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
using (var ms = new MemoryStream()) {
fs.CopyTo(ms);
var content = ms.ToArray();
Console.WriteLine($"File readed, {content.Length} bytes.");
}
}
}
catch (Exception ex) {
Console.WriteLine("*** Error ***");
Console.WriteLine(ex.Message);
}
如此即可成功讀檔,反覆執行可觀察到長度持續增加,確認 file-locker 仍開啟檔案持續寫入。
再多做一個測試,如果寫入程式在建立 FileStream 時指定 FileShare.None (拒絕共用目前檔案。 任何 (由這個處理序或其他處理序) 開啟檔案的要求將會失敗,直到關閉檔案。 參考),如此即便使用 FileStream 宣告 FileShare.ReadWrite,一樣會出現 is being used by another process 錯誤:
using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
收工。
Tips of how to read the file opened by another process with FileShare.ReadWrite parameter.
Comments
# by Winson
Thanks for sharing~
# by GregYu
如果, [空間]、[權限] 許可,把 Log 複製一份, 分析的時候用副本,也許可以避開