最可靠判断文件是否被占用的方法是用目标操作相同模式尝试打开文件并检查异常HResult值:若为-2147024864(0x80070020,ERROR_SHARING_VIOLATION),则判定被占用;其他IOException需结合具体错误码过滤,避免误判。

直接尝试打开文件是最可靠的判断方式
Windows 没有提供标准 API 让你“查询”一个文件是否被占用,所有基于 Process.GetProcesses() 或扫描句柄的方案都不可靠、易漏判、权限受限。唯一稳定可行的做法是:用你打算后续使用的相同模式和访问权限,尝试打开该文件 —— 如果抛出 IOException 且 InnerException 是 UnauthorizedAccessException 或错误码为 32(ERROR_SHARING_VIOLATION)或 5(ERROR_ACCESS_DENIED),基本可判定被占用。
注意:不要只依赖异常类型,必须检查 HResult 或 Win32ErrorCode,因为 .NET 在不同版本/平台下异常包装行为不一致。
-
File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None)最严格,适合检测“是否完全不可读” - 若你后续要写入,应模拟写入场景:
File.Open(path, FileMode.Open, FileAccess.Write, FileShare.Read) - 避免用
File.Exists()或File.GetAttributes()判断占用 —— 它们成功不代表你能打开
捕获并识别真正的占用异常
不是所有 IOException 都代表“被占用”。常见干扰包括路径不存在、无权限、磁盘已满、网络路径断开等。必须精准过滤:
- 检查
ex.HResult:被占用时通常是-2147024864(即 0x80070020,对应 Win32 错误 32) - 或用
Marshal.GetHRForException(ex)获取原始 HResult(.NET Core/.NET 5+ 更推荐) - 在 Windows 上也可查
ex.InnerException?.HResult,某些封装会把 Win32 码藏在内层 - 不要仅靠
ex.Message.Contains("used by another process")—— 多语言系统下字符串不可靠
示例关键判断逻辑:
try {
using var fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None);
} catch (IOException ex) {
int hresult = Marshal.GetHRForException(ex);
if (hresult == unchecked((int)0x80070020)) { // ERROR_SHARING_VIOLATION
return true; // 被占用
}
}
FileShare 枚举值决定检测灵敏度
同一个文件,不同 FileShare 值会导致“是否报错”完全不同。比如 Excel 默认以 FileShare.Read 打开,此时你用 FileShare.None 尝试打开必失败;但用 FileShare.Read 就可能成功 —— 即使它正在被编辑。
-
FileShare.None:最敏感,只要别人开了任何句柄(哪怕只读)就失败 -
FileShare.Read:允许其他进程同时读,但禁止写 —— 若对方以读写打开,你仍会失败 -
FileShare.Write:同理,允许别人写,但禁止读 - 实际检测前,必须明确你后续操作需要什么共享级别,否则结果无意义
异步操作与长时占用场景需额外处理
文件被占用往往是瞬态的(如杀毒软件临时扫描、Office 自动保存)。单纯一次检查返回“空闲”,不代表你接下来 File.Copy() 就一定成功 —— 中间可能被抢占。
- 对关键操作(如覆盖写入),建议采用“重试 + 指数退避”策略,而非单次检查
- 避免在 UI 线程中长时间阻塞等待,可用
Task.Run(() => CheckIfLocked(path))包裹 - 不要用
Thread.Sleep轮询检测 —— 浪费 CPU 且无法解决根本竞争问题 - 真正健壮的方案是:直接执行目标操作(如
File.Move()),捕获异常并按需重试或提示用户
最常被忽略的一点:即使你确认文件“当前未被占用”,也无法保证它在你调用 File.WriteAllText() 的那一毫秒仍空闲 —— 竞态条件始终存在,防御性编程比预测更有效。










