FileSystemWatcher无法拦截文件操作,真实磁盘故障需在应用层封装可开关的文件访问wrapper,结合ResiliencePipeline注入概率性失败,并通过netsh模拟网络分区,测试后务必清理临时文件、句柄及UNC缓存连接。

用 FileSystemWatcher 拦截文件操作再抛异常行不通
它只通知变化,不参与底层 I/O 控制,没法在 CreateFile 或 WriteFile 系统调用时插手。真要模拟磁盘故障,得绕过 .NET 的抽象层,直接干预 Win32 API 或改写底层驱动——这在生产环境既危险又不可控。
更可行的路径是:在应用层做「故障注入点」,把所有文件访问封装进可开关的 wrapper 中。比如把 File.WriteAllText 替换成自定义的 ResilientFile.WriteAllText,里面加概率性失败逻辑。
- 别试图 hook
FileStream构造函数或重写FileStream.Write—— 它们被标记为sealed或内部调用系统 API,拦截成本高且易崩溃 - 所有路径必须走你自己的 wrapper,包括
Directory.CreateDirectory、File.Exists,否则故障点就漏了 - 测试时开启故障模式,上线前确保开关默认关闭,避免误触发
用 Microsoft.Extensions.Resilience + 自定义 ResiliencePipeline 控制失败率
这个包本身不处理文件系统,但它能帮你把「失败策略」和业务逻辑解耦。关键不是它内置什么功能,而是你怎么把它套到文件操作上。
示例:给 File.Copy 包一层带重试+随机失败的 pipeline:
var pipeline = new ResiliencePipelineBuilder()
.AddTransientHttpErrorFallback(new FallbackStrategyOptions<bool> {
ShouldHandle = args => args.Outcome switch {
{ Exception: IOException ex } => ex.Message.Contains("disk") || ex.HResult == -2147024862, // ERROR_DISK_FULL
_ => false
},
FallbackAction = _ => throw new IOException("Simulated disk full")
})
.Build();</bool>
-
HResult == -2147024862是 Windows 的ERROR_DISK_FULL,比字符串匹配更可靠 - 别只 mock
IOException,真实磁盘故障还可能抛UnauthorizedAccessException(权限突变)、DirectoryNotFoundException(卷意外卸载) - 网络共享路径(如
\servershare)失败时,Win32Exception的NativeErrorCode更准,比如 53(ERROR_BAD_NETPATH)代表网络分区
测试网络分区:别碰 SMB 协议栈,改用本地回环 + netsh interface 模拟断连
C# 代码里没法“让 UNC 路径突然不通”,但你可以让本机网络层丢包或禁用接口,这样所有对 W.0.0.1share 的访问自然超时或拒绝连接。
实操步骤:
- 先建一个本地 SMB 共享(用
net share test=C: emp /GRANT:Everyone,FULL) - 测试前执行:
netsh interface set interface "Ethernet" admin=disabled(注意接口名要匹配) - 恢复用:
netsh interface set interface "Ethernet" admin=enabled - 别用
ping判断连通性——SMB 走 TCP 445,而防火墙可能放行 ICMP 却拦掉端口
这时候 File.Exists(@"\127.0.0.1 estile.txt") 会卡住约 20 秒(系统默认超时),然后抛 IOException 带 NativeErrorCode 64(ERROR_HOST_UNREACHABLE)。
真正容易被忽略的点:时间窗口和状态残留
磁盘满不是瞬间状态,而是一段持续期。你模拟一次 ERROR_DISK_FULL 后,如果后续操作没清空临时文件,下次调用还是满;同理,网络分区恢复后,已建立的 FileStream 可能还卡在 pending 状态,不会自动重连。
- 每次测试完必须手动清理:删掉测试生成的临时文件、调用
GC.Collect()+GC.WaitForPendingFinalizers()强制释放句柄 - 对 UNC 路径,用
net use * /delete /y清掉缓存连接,否则File.Copy可能复用旧会话导致行为不一致 - 别依赖
Thread.Sleep等“恢复时间”——真实故障恢复是异步的,要用事件或轮询确认状态,比如检查DriveInfo.TotalFreeSpace是否回升










