寫了個小工具偷看網站即時 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 複製一份, 分析的時候用副本,也許可以避開

Post a comment