文件共享无法按预期工作

时间:2013-05-14 13:09:21

标签: c# .net file-sharing file-security

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

以下是docs的一些支持引用:

  

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

这是另一个宝石:

  

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

     

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

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

4 个答案:

答案 0 :(得分:15)

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

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

必须包含FileShare.Write。

答案 1 :(得分:1)

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

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

以下比较可能更好地解释了它:

// 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的描述短语:

  

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

应该被理解为

  

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

[更新]

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

The internal FileStream ctor

The internal FileStream Init method

答案 2 :(得分:1)

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

dwShareMode参数的讨论中,它说:

  

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

     

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

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

答案 3 :(得分:0)

您传递的第四个参数

<强>份额
一个常量,用于确定进程如何共享文件。

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