应捕获 IOException 并指数退避重试,限3–5次,间隔从10ms逐次翻倍;仅捕获IOException,避免UnauthorizedAccessException;重试用Task.Delay;调整FileStream的FileShare参数以支持多读共存。

文件被占用时 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 是关键:
- 多个读者共存 → 用
- 一个写者 + 多个读者 → 用
(写入方必须独占Write,但允许别人只读) - 完全不允许并发 → 保持
,但要确保持有时间最短
示例:
using var fs = new FileStream(path, FileMode.Open, FileAccess.Write, FileShare.Read);这样别的进程还能
OpenRead,但不能 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");
if (mutex.WaitOne(1000)) {
try {
File.AppendAllText(logPath, message);
} finally {
mutex.ReleaseMutex();
}
} else {
// 超时,说明别人正在写,可降级处理或抛自定义异常
}
真正难的不是加锁,而是判断哪些操作必须串行、哪些可以并行,以及锁粒度是否合理——锁整个文件还是只锁某段内容,差别很大。










