【问题标题】:Asynchronous file IO in .Net.Net 中的异步文件 IO
【发布时间】:2008-09-18 00:41:05
【问题描述】:

我正在用 C# 构建一个玩具数据库,以了解有关编译器、优化器和索引技术的更多信息。

我想在将页面带入缓冲池的(至少是读取)请求之间保持最大并行度,但我对如何在 .NET 中最好地实现这一点感到困惑。

以下是一些选项以及我遇到的问题:

  1. 使用System.IO.FileStreamBeginRead 方法

    但是,文件中的位置不是BeginRead 的参数,它是FileStream 的属性(通过Seek 方法设置),所以我一次只能发出一个请求并且必须在持续时间内锁定流。 (或者我是吗?文档不清楚如果我只在SeekBeginRead 调用之间持有锁但在调用EndRead 之前释放它会发生什么。有人知道吗?)我知道该怎么做,我只是不确定这是不是最好的方法。

  2. 似乎还有另一种方式,围绕System.Threading.Overlapped 结构和P\Invoke 到kernel32.dll 中的ReadFileEx 函数。

    不幸的是,样本很少,尤其是托管语言。这条路线(如果它可以工作的话)显然还涉及ThreadPool.BindHandle方法和线程池中的IO完成线程。我的印象是这是在 windows 下处理这种情况的认可方式,但我不明白,我找不到对初学者有帮助的文档的入口点。

  3. 还有什么?

  4. 在评论中,jacob 建议为每次读取创建一个新的FileStream

  5. 将整个文件读入内存。

    如果数据库很小,这将起作用。代码库很小,还有很多其他的低效率,但数据库本身不是。我还想确保我正在做处理大型数据库所需的所有簿记(事实证明这是复杂性的很大一部分:分页,外部排序,......)我担心它可能也是容易不小心作弊。

编辑

澄清为什么我对解决方案 1 持怀疑态度:从 BeginRead 到 EndRead 一直持有一个锁意味着我需要阻止任何想要启动读取的人,因为另一次读取正在进行中。这感觉不对,因为启动新读取的线程可能(通常)能够在结果可用之前做更多的工作。 (实际上,写这篇文章让我想到了一个新的解决方案,我把它作为一个新的答案。)

【问题讨论】:

    标签: .net windows winapi file-io asynchronous


    【解决方案1】:

    我不确定我明白为什么选项 1 不适合你。请记住,您不能让两个不同的线程同时尝试使用同一个 FileStream - 这样做肯定会给您带来问题。 BeginRead/EndRead 旨在让您的代码在发生潜在的昂贵 IO 操作时继续执行,而不是启用对文件的某种多线程访问。

    所以我建议你先寻找然后做一个 beginread。

    【讨论】:

    • 同意;您应该为每次运行中的异步读取使用一个新的 FileStream 对象。
    【解决方案2】:

    我们所做的是在 C++/CLI 中围绕 I/O 完成端口、ReadFile 和 GetQueuedCompletion 状态编写一个小层,然后在操作完成时回调到 C#。我们在 BeginRead 和 c# 异步操作模式上选择了这条路线,以提供对用于从文件(或套接字)读取的缓冲区的更多控制。与每次读取时在堆上分配新字节 [] 的纯托管方法相比,这是一个相当大的性能提升。

    此外,互联网上有更多完整的使用 IO 完成端口的 C++ 示例

    【讨论】:

    • 这是个好主意。您还可以通过在创建(或增长)缓冲池时将它们预先分配成大块来避免分配新的 byte[](并破坏大对象堆)。
    • 另外,我现在不知道 GetQueuedCompletionStatus (或以某种方式阅读过去),这可能解释了为什么我的尝试失败了。是时候阅读更多内容了。
    【解决方案3】:

    如果您先将资源(文件数据或其他)加载到内存中,然后跨线程共享会怎样?因为它是一个小数据库。 - 你不会有那么多问题要处理。

    【讨论】:

    • 这在某些情况下有效,但我的意思是“小”是指“功能少”而不是“数据不多”。
    【解决方案4】:

    使用方法#1,但是

    1. 当请求进入时,获取锁 A。使用它来保护待处理的读取请求队列。将其添加到队列中并返回一些新的异步结果。如果这导致第一次添加到队列中,请在返回之前调用第 2 步。在返回之前释放锁 A。

    2. 当读取完成(或由步骤 1 调用)时,获取锁 A。使用它来保护从队列中弹出读取请求。获取锁 B。用它来保护Seek -> BeginRead -> EndRead 序列。释放锁 B. 更新步骤 1 为该读取操作创建的异步结果。 (读取操作完成后,再次调用它。)

    这解决了不阻塞任何线程的问题,因为另一个读取正在进行,但仍然对读取进行排序,以免文件流的当前位置混乱。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-10
      • 1970-01-01
      相关资源
      最近更新 更多