.Net中的异步文件IO

时间:2020-03-06 14:19:33  来源:igfitidea点击:

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

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

以下是一些选项以及每个选项所遇到的问题:

  • 使用System.IO.FileStreamBeginRead方法但是,文件中的位置不是BeginRead的参数,而是FileStream的一个属性(通过Seek方法设置),因此我一次只能发出一个请求,并且必须在此期间锁定流。 (还是我?文档尚不清楚,如果我仅在SeekBeginRead调用之间保持了锁定,而在调用EndRead之前将其释放了。有人知道吗?)我知道该怎么做,我只是不确定这是最好的方法。
  • 似乎还有另一种方法,围绕" System.Threading.Overlapped"结构和P \ Invoke到kernel32.dll中的" ReadFileEx"函数。不幸的是,缺少样本,尤其是在托管语言中。该路由(如果可以使之工作)显然也涉及ThreadPool.BindHandle方法和线程池中的IO完成线程。我得到的印象是,这是在Windows下处理这种情况的认可方法,但是我不理解它,也找不到适合初学者的文档入门。
  • 还有别的吗
  • 在评论中,雅各布建议为飞行中的每次读取创建一个新的FileStream
  • 将整个文件读入内存。如果数据库很小,这将起作用。代码库很小,并且还有许多其他低效率的地方,但是数据库本身不是。我还想确定自己正在做所有处理大型数据库所需的簿记工作(事实证明,这是复杂性的很大一部分:分页,外部排序等),我担心它可能也是如此容易不小心作弊。

编辑

弄清为什么我对解决方案1感到怀疑的原因:从BeginRead到EndRead一直保持一个锁,这意味着我需要阻止仅由于正在进行另一个读取而要发起读取的任何人。感觉不对,因为发起新读取的线程通常可以在结果可用之前做更多的工作。 (实际上,写这篇文章使我想出了一个新的解决方案,作为新的答案。)

解决方案

我不确定为什么选项1对我们不起作用。请记住,我们不能有两个不同的线程试图同时使用同一FileStream,这肯定会给我们带来麻烦。 BeginRead / EndRead的目的是让代码在潜在的昂贵IO操作发生时继续执行,而不是启用对文件的某种多线程访问。

因此,我建议我们先寻找然后开始阅读。

如果先将资源(文件数据或者其他内容)加载到内存中,然后在线程之间共享,该怎么办?由于这是一个小数据库。我们将没有太多要处理的问题。

使用方法1,但是

  • 当请求进入时,请使用锁A。使用它来保护未决读取请求的队列。将其添加到队列中并返回一些新的异步结果。如果这导致第一次添加队列,请在返回之前调用步骤2. 返回前释放锁A。
  • 读取完成时(或者由步骤1调用),请锁定A。使用它来保护从队列中弹出读取请求。拿起锁B。用它来保护Seek->BeginRead->EndRead序列。释放锁B。更新由第1步为此读取操作创建的异步结果。 (由于读取操作完成,请再次调用此方法。)

这解决了不仅仅因为正在进行另一次读取而阻止任何开始读取的线程的问题,而是仍然对读取进行排序,从而不会弄乱文件流的当前位置。

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

另外,在互连网上有很多更完整的C ++使用IO完成端口的示例。