FileStream 共享陷阱:为什么设置了 FileShare.Read 依然报错?
2025-11-21 20:50:28

#为什么明明允许“共享读”,却还是被锁了?

最近在写一段 C# 文件处理逻辑时,我掉进了一个看似简单却颇具迷惑性的坑。需求很简单:一个线程在写日志,另一个线程想要实时读取。
我理所当然地写下了这段代码:

#案发现场与解决方法

1
2
3
4
5
6
7
8
string fileName = "test.txt";

// 1. 打开文件进行写入,并在 FileShare 中大度地允许“Read”(允许他人读取)
var fs1 = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.Read);

// 2. 尝试打开文件进行读取,我也设置了 FileShare.Read(我也允许他人读取)
// 砰!System.IO.IOException: ... process cannot access the file ...
var fs2 = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);

这看起来很不合理,对吧?fs1 说“我允许别人读”,fs2 说“我要读”。明明两情相悦,为什么系统报了 IOException

根本原因解析:
问题的关键在于 fs2 构造函数中的 FileShare.Read 参数。

  • FileAccess 是我想做什么。
  • FileShare 是我允许别人(也就是文件上已存在的其他句柄)做什么。

fs2 尝试打开文件时,操作系统会进行双向检查:

  1. fs2 请求 FileAccess.Read。检查 fs1 的共享设置:fs1 声明了 FileShare.Read通过。
  2. fs2 声明了 FileShare.Read。这意味着 fs2 告诉操作系统:“在这个文件上,我只允许别人进行操作”。检查 fs1 的当前权限:fs1 正在持有 FileAccess.Write

冲突爆发: fs2 的共享策略(只许读,不许写)与 fs1 正在做的事情(写)发生了冲突。因此,Windows 拒绝了 fs2 的请求。

解决方法:
fs2 必须显式地宽容 fs1 的写入行为。我们需要将 fs2 的共享模式改为 ReadWrite

1
2
// 修正后的代码:fs2 必须允许其他人“Write”,因为 fs1 确实正在 Write
var fs2 = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

#底层真相:这是 Windows 的锅 (Win32 API 复现)

有人可能会怀疑这是 .NET 框架本身设计的“矫情”。为了自证清白,我直接使用 C++ 调用 Win32 API CreateFile 来复现这个场景。你会发现,结果一模一样。
在 Windows 内核中,文件共享机制是非常严格的。

  • FileAccess.Write 对应 GENERIC_WRITE
  • FileShare.Read 对应 FILE_SHARE_READ

下面是 C++ 的复现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <windows.h>
#include <iostream>

int main() {
LPCWSTR fileName = L"test.txt";

// 1. 模拟 C# 的 fs1:写入模式,共享读
HANDLE hFile1 = CreateFile(
fileName,
GENERIC_WRITE, // FileAccess.Write
FILE_SHARE_READ, // FileShare.Read
NULL,
CREATE_ALWAYS, // FileMode.Create
FILE_ATTRIBUTE_NORMAL,
NULL
);

if (hFile1 == INVALID_HANDLE_VALUE) {
std::cerr << "fs1 创建失败" << std::endl;
return 1;
}
std::cout << "fs1 创建成功 (Handle Open)" << std::endl;

// 2. 模拟 C# 的 fs2:读取模式,共享读
// 这里会失败,因为我们传了 FILE_SHARE_READ,这意味着我们不允许别人(hFile1)拥有 WRITE 权限
HANDLE hFile2 = CreateFile(
fileName,
GENERIC_READ, // FileAccess.Read
FILE_SHARE_READ, // FileShare.Read (这里就是祸源)
NULL,
OPEN_EXISTING, // FileMode.Open
FILE_ATTRIBUTE_NORMAL,
NULL
);

if (hFile2 == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
// 错误代码 32 (0x20): The process cannot access the file because it is being used by another process.
std::cerr << "fs2 打开失败! GetLastError: " << error << std::endl;
std::cerr << "原因: 第二个句柄的共享策略(FILE_SHARE_READ)与第一个句柄的访问权限(GENERIC_WRITE)冲突。" << std::endl;
} else {
std::cout << "fs2 打开成功" << std::endl;
CloseHandle(hFile2);
}

CloseHandle(hFile1);
return 0;
}

#总结

不要被 FileShare 的名字误导,以为它只是“我开放给别人的权限”。在第二次打开文件时,它实际上变成了一个过滤器:它必须兼容文件上当前已存在的所有句柄的权限。
如果你的程序中有个 Writer 正在工作,任何后来的 Reader 想要加入,都必须在自己的 FileShare 中显式包含 Write,承认 Writer 的存在。

#相关阅读

File sharing not working as expected