file.copy磁盘故障需手动重试并判错:捕获ioexception,检查hresult为0x80070015等码,对设备未就绪指数退避重试3次;多磁盘场景用diskrouter前置选盘,懒检测健康状态;跨卷move须拆解为复制+manifest记录+删除,确保可回退;日志写入要autoflush或定时flush防缓冲掩盖故障。

磁盘故障时 File.Copy 直接抛 IOException 怎么办
它不区分“磁盘暂时忙”和“磁盘已离线”,只要底层 CreateFile 失败,就立刻 throw。这不是设计缺陷,而是 Windows API 的默认行为——C# 只是透传了这个语义。
实操上必须自己兜底重试 + 判定错误类型:
- 捕获
IOException后检查InnerException?.HResult是否为0x80070015(设备未就绪)、0x80070070(磁盘空间不足,但可能实为坏道假报)、或0x80070020(进程正占用) - 对
0x80070015做指数退避重试(比如 1s、2s、4s),最多 3 次;超过则认为磁盘已不可用 - 避免在重试中反复调用
File.Exists——它本身也会触发磁盘访问,可能加重故障
单机多磁盘场景下如何让 FileStream 自动降级到备用盘
不能靠 try-catch 后手动切路径,因为 FileStream 构造即打开句柄,失败就结束了。得把“选择哪块盘”从 IO 路径里抽出来,变成前置决策。
推荐做法是封装一个 DiskRouter 类,内部维护磁盘健康状态缓存:
- 启动时用
DriveInfo.GetDrives()扫描所有本地固定磁盘,逐个执行new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None, 1, FileOptions.RandomAccess)测试可写性(只开不读写,快速验证) - 将健康盘按剩余空间排序,故障盘标记为
Unhealthy并记录最后失败时间 - 业务调用
DiskRouter.GetWritablePath("myapp/logs")时,它返回当前最优路径,而非硬编码盘符
注意:不要每秒轮询磁盘状态,改用“首次访问失败 + 延迟 30 秒后重新探测”的懒检测策略,减少干扰。
Directory.Move 跨卷失败时,如何无损回退到复制+删除
跨卷移动本质是复制+删除,但 Directory.Move 把这两步锁死在原子操作里,一旦中间出错(比如目标盘突然满),源目录可能已部分清空,无法恢复。
必须拆解成可控步骤:
- 先用
Directory.EnumerateFiles(source, "*", SearchOption.AllDirectories)获取全量文件路径快照(内存小,不占磁盘) - 逐个
File.Copy到目标,每成功一个就记录到临时.move_manifest.json文件(写入目标盘根目录) - 全部复制完成后,再执行源目录递归删除;若删除中途失败,根据 manifest 反向清理目标盘残留文件
- 关键点:
.move_manifest.json必须用File.WriteAllText(path, json, Encoding.UTF8)+FileOptions.WriteThrough确保落盘,否则断电时 manifest 丢失,回退失效
日志写入卡住时,StreamWriter 缓冲区会掩盖磁盘故障
默认 StreamWriter 开启缓冲,即使磁盘已掉线,WriteLine 仍返回成功,直到缓冲区满或 Flush 时才暴露问题——这会让故障感知延迟几十秒甚至更久。
生产环境必须关闭自动缓冲或强制同步:
- 构造时传
new StreamWriter(stream) { AutoFlush = true },但注意性能损耗(每次写都落盘) - 更平衡的做法:保持缓冲,但每 5 秒调用一次
streamWriter.Flush(),并在catch (IOException)后立即尝试stream.Flush()验证底层是否存活 - 绝对不要依赖
Dispose触发的隐式Flush——进程崩溃时它根本不会执行
真正难处理的是“磁盘响应极慢但不死”,这时候需要单独起一个 watchdog 线程,用 stream.BeginWrite + 超时判断,而不是等同步写卡住整个主线程。








