应捕获 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 是关键:
- 多个读者共存 → 用
</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 应用未显式
DisposeFileStream或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>}真正难的不是加锁,而是判断哪些操作必须串行、哪些可以并行,以及锁粒度是否合理——锁整个文件还是只锁某段内容,差别很大。
- 对源文件,确认它没被其他程序以










