文件共享无法按预期工作

我有一个文件共享问题,我的进程正在尝试读取日志文件,而它当前仍由NLog打开。 在诊断问题时,我发现了令人惊讶的事情。 以下失败:

using (var fileStream1 = new FileStream("test.file", FileMode.Append, FileAccess.Write, FileShare.Read)) using (var fileStream2 = new FileStream("test.file", FileMode.Open, FileAccess.Read, FileShare.Read)) { } 

第二个FileStream构造函数调用失败,其中:

 System.IO.IOException was unhandled Message=The process cannot access the file 'c:\...\test.file' because it is being used by another process. Source=mscorlib StackTrace: at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath) at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share) 

尽管事实上第一个FileStream表明它愿意分享阅读。 我发现更令人惊讶的是,这有效:

 using (var fileStream1 = new FileStream("test.file", FileMode.Append, FileAccess.Write, FileShare.Read)) using (var fileStream2 = new FileStream("test.file", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { } 

嗯,是的,在打开第二个流时请求更多访问实际上绕过了问题。 我完全不知道为什么会这样,只能假设我误解了一些事情。 我已经阅读了API文档,但他们只支持我当前的心智模型,了解它应该如何工作,这与它的工作方式相反。

以下是文档中的一些支持引用:

此枚举的典型用法是定义两个进程是否可以同时从同一文件读取。 例如,如果打开文件并指定了Read,则其他用户可以打开文件进行读取但不能写入。

这是另一个gem:

以下FileStream构造函数打开现有文件并授予对其他用户的只读访问权限(读取)。

FileStream s2 = new FileStream(name, FileMode.Open, FileAccess.Read, FileShare.Read);

任何人都可以对这种行为有所了解。 我在.NET 4%Windows XP上测试它。

  var fileStream2 = new FileStream(..., FileShare.Read) 

这让很多程序员兴奋不已。 每个人都认为这增加了阅读共享。 它没有,原始文件访问请求已经允许读取并再次指定它不会改变任何东西。 相反,它否认写共享。 这是行不通的,因为有人已经获得了写入权限。 并且正在使用它,你无法删除该权利。 因此,您访问该文件的请求将失败。

必须包含FileShare.Write。

实际发生的是, fileStream2无法更改对fileStream2已写入(或追加)的文件的后续访问。

fileStream2将成功打开文件,只有当没有已经具有Write文件访问权限的进程时,才会将FileShare.Read作为“遗留”进行后续访问。 更重要的是,在我们的例子中,我们谈论的是同一个过程。 从另一个文件流修改文件流的属性没有多大意义,不是吗?

也许以下比较更好地解释了它:

 // works using (var fileStream1 = new FileStream("test.file", FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite)) using (var fileStream2 = new FileStream("test.file", FileMode.Append, FileAccess.Write, FileShare.Read)) using (var fileStream3 = new FileStream("test.file", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { } // fails using (var fileStream1 = new FileStream("test.file", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)) using (var fileStream2 = new FileStream("test.file", FileMode.Append, FileAccess.Write, FileShare.Read)) using (var fileStream3 = new FileStream("test.file", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { } 

在我看来, FileShare.Read的描述短语:

允许随后打开文件进行读取。

应该读作

对文件的后续访问仅限于读取,包括对现有锁的访问。

[更新]

我没有解析代码,但似乎这两个链接可以揭示构造函数的内部function:

内部FileStream ctor

内部FileStream Init方法

我想我已经在CreateFile的文档中找到了答案。

在讨论dwShareMode参数时,它说:

FILE_SHARE_READ 0x00000001在文件或设备上启用后续打开操作以请求读取访问权限。 否则,如果其他进程请求读取访问权限,则无法打开该文件或设备。 如果未指定此标志,但已打开文件或设备以进行读取访问,则该函数将失败。

FILE_SHARE_WRITE 0x00000002在文件或设备上启用后续打开操作以请求写访问。 否则,如果其他进程请求写访问权限,则无法打开该文件或设备。 如果未指定此标志,但文件或设备已打开以进行写访问或具有带写访问权的文件映射,则该函数将失败。

这从根本上改变了我对文件共享如何工作的理解。

你传递的第四个参数

分享
一个常量,用于确定进程如何共享文件。

确定其他人可以打开文件的模式。 很明显 – 当您尝试使用fileshare模式“read”打开文件并且已经在写入模式下打开相同的文件时 – 操作失败。