文件被占用时 IOException
怎么捕获和重试
直接对正在被其他进程或线程读写的文件调用
File.Open或
File.WriteAllText,大概率抛出
IOException:“The process cannot access the file 'xxx' because it is being used by another process.”。这不是异常逻辑错误,而是典型并发竞争现象。
正确做法不是回避,而是主动处理:用
try/catch捕获
IOException,配合指数退避(exponential backoff)重试。注意不要无限制循环——建议最多 3–5 次,间隔从 10ms 开始逐次翻倍。 只捕获
IOException,不捕获
UnauthorizedAccessException等权限类异常,它们代表不同问题 重试前加
Thread.Sleep或更推荐的
await Task.Delay(异步上下文里) 重试逻辑别塞进业务主流程,抽成独立方法,比如
RetryOnFileLockAsync
FileStream
构造时如何设置 FileShare
参数
根本原因常在于打开方式太“霸道”。默认
File.OpenRead(path)等价于
new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None)—— 它禁止任何其他进程/线程同时访问该文件。
按实际协作需求调整
FileShare是关键: 多个读者共存 → 用
</li> <li>一个写者 + 多个读者 → 用 <code<FileShare.Read(写入方必须独占
Write,但允许别人只读) 完全不允许并发 → 保持
,但要确保持有时间最短</li> </ul> <p>示例:<pre class='brush:php;toolbar:false;'>using var fs = new FileStream(path, FileMode.Open, FileAccess.Write, FileShare.Read);这样别的进程还能
OpenRead,但不能 <code>OpenWrite。
为什么 File.Copy
和 File.Move
也报文件锁定
很多人以为复制/移动是“原子操作”,不会冲突——其实不然。
File.Copy内部会先
Create目标文件,再
Read源文件流,全程可能被中断;
File.Move在跨卷时本质是“复制+删除”,同样涉及多次文件访问。
解决思路一致:
对源文件,确认它没被其他程序以FileShare.None打开 对目标路径,确保没有同名文件正被写入(比如日志轮转中未关闭的
StreamWriter) 跨进程场景下,优先考虑用命名互斥体(
Mutex)协调,而不是靠重试硬扛
特别注意:Windows 中杀掉进程不一定立即释放句柄,尤其是 .NET 应用未显式
Dispose
FileStream或
StreamWriter时,GC 回收前文件锁一直挂着。
用 Mutex
实现跨进程文件访问协调
当重试和
FileShare都不够用(比如多个独立进程需严格串行写同一配置文件),就得上同步原语。
Mutex是 Windows 下最轻量、跨进程有效的选择。
关键点:
名称必须全局唯一,建议带产品名/路径哈希,如"Global\MyApp_Config_Write_" + path.GetHashCode()务必用
try/finally确保
mutex.ReleaseMutex()被执行,否则死锁 不要在
Mutex持有期间做耗时操作(如网络请求、大文件读写),否则阻塞其他进程太久
示例节选:
var mutex = new Mutex(false, "Global\MyApp_Log_Write");<br>if (mutex.WaitOne(1000)) {<br> try {<br> File.AppendAllText(logPath, message);<br> } finally {<br> mutex.ReleaseMutex();<br> }<br>} else {<br> // 超时,说明别人正在写,可降级处理或抛自定义异常<br>}
真正难的不是加锁,而是判断哪些操作必须串行、哪些可以并行,以及锁粒度是否合理——锁整个文件还是只锁某段内容,差别很大。
