文件共享未按预期工作

本文关键字:工作 文件共享 | 更新日期: 2023-09-27 18:13:22

我遇到了一个文件共享问题,我的进程正在尝试读取日志文件,而该文件当前仍由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)

尽管第一CCD_ 2表示其愿意共享阅读。我发现更令人惊讶的是,这是有效的:

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文档,但它们只是支持我当前的心理模型,即它应该如何工作,而不是它的工作方式。

以下是文档中的一些支持性报价:

此枚举的典型用途是定义是否有两个进程可以同时从同一文件中读取。例如,如果文件如果已打开并指定了"读取",则其他用户可以打开该文件阅读而非写作。

这是另一颗宝石:

以下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无法更改对fileStream1已打开进行写入(或追加(的文件的后续访问。

只有在没有进程已经拥有Write文件访问权限的情况下,fileStream2才会成功打开文件,并将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:的描述短语

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

应读取为

对文件的后续访问仅限于读取,包括对已经存在的锁的访问。

[更新]

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

内部FileStream ctor

内部FileStream Init方法

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

在讨论dwShareMode参数时,它说:

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

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

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

通过的第四个参数

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

确定其他人可以以何种模式打开文件。很明显,当您试图以文件共享模式"读取"打开文件,并且已经以写入模式打开了相同的文件时,操作会失败。