17

我有一个文件共享问题,我的进程正在尝试读取一个日志文件,而它目前仍由 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 文档,但它们只是支持我当前的心理模型,说明它应该如何工作,与它的工作方式相反。

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

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

这是另一个宝石:

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

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

任何人都可以阐明这种行为。我正在.NET 4 % Windows XP 上对此进行测试。

4

4 回答 4

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

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

必须包括 FileShare.Write。

于 2013-05-14T22:30:43.070 回答
1

实际发生的情况是,fileStream2无法更改对已打开以供写入(或附加)的文件的后续访问fileStream1

fileStream2FileShare.Read仅当没有进程已对其具有Write文件访问权限时,才会成功打开文件并将 a作为“遗留”以供后续访问。更重要的是,在我们的示例中,我们讨论的是相同的过程。从另一个文件流修改一个文件流的属性并没有太大意义,不是吗?

也许下面的比较可以更好地解释它:

// 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 方法

于 2013-05-14T14:05:44.317 回答
1

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

dwShareMode参数的讨论中,它说:

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

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

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

于 2013-05-14T18:43:44.267 回答
0

您传递的第四个参数

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

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

于 2013-05-14T13:15:07.300 回答