FileShare.ReadWrite是解决“文件正由另一进程使用”错误的关键起点,它允许当前只读操作同时兼容其他进程的读写,但需配合HResult码校验、正确资源释放及重试机制才能可靠运行。

FileShare.ReadWrite 不是万能解药,但它是关键起点
直接用 File.OpenRead 或 File.OpenText 读日志文件时抛出“正由另一进程使用”,根本原因不是你代码写错了,而是默认的 FileShare 值是 FileShare.Read —— 它只允许其他进程同时读,不允许写。而大多数日志组件(如 NLog、log4net)正是以 FileAccess.Write + FileShare.Read 打开文件的,两者冲突,必然报错。
正确做法是显式指定 FileShare.ReadWrite:
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var sr = new StreamReader(fs, Encoding.UTF8))
{
var content = sr.ReadToEnd();
}-
FileShare.ReadWrite表示:我只读,但允许别人同时读或写 —— 这才匹配日志写入器的行为 - 不要用
FileShare.None,那等于锁死整个文件,连自己都可能被卡住 - 如果目标文件还可能被独占写(比如某些备份工具),
FileShare.ReadWrite仍可能失败,这时得配合重试
为什么 try-catch IOException + HResult == 32 才算真正可靠
Windows 层面的“文件被占用”错误码是 32(ERROR_SHARING_VIOLATION),但 .NET 会把底层 Win32 错误包装成 IOException,且不同版本对 InnerException 的处理不一致 —— 有些直接吞掉原始错误码,有些又塞进 HResult。
所以不能只写 catch (IOException) 就认定是占用问题:
- 必须检查
ex.HResult == -2147024864(即 32 的补码)或更稳妥地用Marshal.GetHRForException(ex) == unchecked((int)0x80070020) - 避免依赖
ex.Message.Contains("占用")—— 中文系统可用,英文环境就失效 - 常见干扰项:
UnauthorizedAccessException(权限不足)和FileNotFoundException(路径错)长得像,但根本不是占用问题
FileStream 没关干净?using 块里嵌套 using 是最省心的防线
很多人以为只要写了 using (FileStream fs = ...) 就万事大吉,结果在 StreamReader 外层再套一层 using,却忘了 StreamReader 默认会关闭底层流 —— 导致 fs.Dispose() 被调两次,第二次抛 ObjectDisposedException,掩盖了真正的占用问题。
安全写法只有两种:
- 让
StreamReader管理流生命周期:using (var sr = new StreamReader(path, Encoding.UTF8)) { ... }—— 它内部已用FileShare.ReadWrite打开 - 手动控制流,且明确禁止
StreamReader关流:using (var sr = new StreamReader(fs, Encoding.UTF8, true, 1024, leaveOpen: true)) - 绝对不要在同一个流上套两层
using,也不要用fs.Close()+using混用
重试逻辑不是可选功能,而是生产环境的刚需
即使设置了 FileShare.ReadWrite,在高并发读写日志场景下,仍可能因毫秒级窗口错过而失败。此时硬等或直接报错都会影响用户体验。
一个轻量但够用的重试模板:
for (int i = 0; i < 3; i++)
{
try
{
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var sr = new StreamReader(fs, Encoding.UTF8))
{
return sr.ReadToEnd();
}
}
catch (IOException ex) when (GetWin32ErrorCode(ex) == 32)
{
if (i == 2) throw;
Thread.Sleep(100 * (int)Math.Pow(2, i)); // 100ms → 200ms → 400ms
}
}- 指数退避比固定延时更抗抖动
- 最多 3 次足够覆盖绝大多数瞬时冲突,再多次大概率是设计问题(比如日志写入器没做缓冲)
- 别在 UI 线程里用
Thread.Sleep,改用await Task.Delay配合 async/await
真正容易被忽略的点:FileShare 设置只是“协商许可”,它不强制其他进程遵守 —— 如果对方以 FileShare.None 打开,你的 ReadWrite 请求依然会被拒绝。这时候,唯一出路是协调日志组件改用共享模式,或者换用内存映射文件(MemoryMappedFile)这类更高级的 IPC 方案。


















