【问题标题】:Reading a file with FileStream and FILE_FLAG_NO_BUFFERING使用 FileStream 和 FILE_FLAG_NO_BUFFERING 读取文件
【发布时间】:2016-04-28 22:04:18
【问题描述】:

一点背景知识:在对大文件进行 IO 时,我一直在尝试使用 FILE_FLAG_NO_BUFFERING 标志。我们正在尝试减少缓存管理器的负载,希望通过后台 IO,我们将减少我们的应用程序对用户计算机的影响。性能不是问题。尽可能多地在幕后工作是一个大问题。我有一个接近工作的包装器来做无缓冲的 IO,但我遇到了一个奇怪的问题。当我使用不是 4 的倍数的偏移量调用 Read 时出现此错误。

句柄不支持同步操作。 FileStream 构造函数的参数可能需要更改以指示句柄是异步打开的(即,它是为重叠 I/O 显式打开的)。

为什么会这样?这条信息不是自相矛盾吗?如果我添加异步文件选项,我会得到一个 IOException(参数不正确。)

我想真正的问题是这些要求,http://msdn.microsoft.com/en-us/library/windows/desktop/cc644950%28v=vs.85%29.aspx,与这些 4 的倍数有什么关系。

下面是演示问题的代码:

FileOptions FileFlagNoBuffering = (FileOptions)0x20000000;
int MinSectorSize = 512;
byte[] buffer = new byte[MinSectorSize * 2];
int i = 0;
while (i < MinSectorSize)
{
    try
    {
        using (FileStream fs = new FileStream(@"<some file>", FileMode.Open, FileAccess.Read, FileShare.None, 8, FileFlagNoBuffering | FileOptions.Asynchronous))
        {
            fs.Read(buffer, i, MinSectorSize);
            Console.WriteLine(i);
        }
    }
    catch { }
    i++;
}
Console.ReadLine();

【问题讨论】:

  • 该标志未包含在 FileOptions 枚举中可能是有原因的。此外,使用该标志时必须遵守requirements; FileStream 可能不会跟随它们,因为它没有公开该标志。
  • 是的,但它存在于底层 Win32 API 中是有原因的。我知道有要求。我已经多次阅读该链接。如果您告诉它,FileStream 将跟随它们。 “读取和写入操作的文件访问缓冲区地址应该是物理扇区对齐的,这意味着在内存中的地址对齐是卷的物理扇区大小的整数倍”与 4 有什么关系?
  • 值 4 在这里是相关的,因为这是 CLR 为 32 位进程中的托管对象地址保证的对齐方式。否则为 32 位操作系统的幻数。否则,这与磁盘驱动程序所接受的内容几乎没有关系。您可以通过在非托管内存 Marshal.AllocHGlobal() 中分配内存来获得扇区对齐,过度分配和调整起始地址以获得对齐。否则这个选项没有暴露在框架中的原因。
  • 你真的应该更多地使用 .NET。 FileStream 之类的类是非常 薄包装器。它明确允许 FILE_FLAG_NO_BUFFERING,没有例外。
  • @HansPassant 我没有找到说明FILE_FLAG_NO_BUFFERING 支持FileOption 的文档,尽管您是对的 - 它正在传递给CreateFile

标签: c# windows


【解决方案1】:

使用FILE_FLAG_NO_BUFFERING 时,记录的要求是读取或写入的内存地址必须是物理扇区大小的倍数。在您的代码中,您允许随机选择字节数组的地址(因此不太可能是物理扇区大小的倍数),然后添加偏移量。

您观察到的行为是,如果偏移量是 4 的倍数,则调用有效。字节数组可能与 4 字节边界对齐,因此如果内存地址为4的倍数。

因此,您的问题可以这样改写:为什么当内存地址是 4 的倍数时读取工作正常,而文档说它必须是 512 的倍数?

答案是,如果您违反规则,文档并没有对会发生什么做出任何具体保证。无论如何,呼叫都可能起作用。无论如何,电话可能会奏效,但仅在偶数年的 9 月。无论如何调用都可能会发生,但前提是内存地址是 4 的倍数。(这可能取决于读取操作中涉及的特定硬件和设备驱动程序。仅仅因为它在您的机器上工作不会'不是说它适用于其他任何人。)

首先将FILE_FLAG_NO_BUFFERINGFileStream 一起使用可能不是一个好主意,因为我怀疑FileStream 实际上会保证它将您提供的地址未经修改地传递给底层ReadFile称呼。相反,使用 P/Invoke 直接调用底层 API 函数。您可能还需要以这种方式分配内存,因为我不知道 .NET 是否提供任何方法来分配具有特定对齐方式的内存。

【讨论】:

  • 如果 FileStream 没有将字节数组的实际地址传递给底层的 Readfile,它会不会出现可怕的问题?到目前为止,我的方法没有任何问题,除非尝试使用非零偏移量。
  • 在某些情况下,FileStream 可能会在将数据复制到您的数组之前将数据读取到它自己的临时缓冲区中。我猜它没有,但你不想依赖猜测你是否可以帮助它。更重要的是,您应该遵循与物理扇区大小保持一致的书面要求,即使它似乎在您不这样做时也可以工作
【解决方案2】:

只需直接用FILE_FLAG_NO_BUFFERING调用CreateFile,然后在用FileStream打开之前关闭它就可以达到同样的效果。

【讨论】:

  • 我建议对这个答案进行投票。我已经对其进行了测试,它的工作原理与@user2882190 指示的一样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-07
  • 2016-09-26
  • 2012-12-17
  • 1970-01-01
  • 1970-01-01
  • 2012-07-02
相关资源
最近更新 更多