.Net中的异步文件IO
-
01-07-2019 - |
题
我正在用 C# 构建一个玩具数据库,以了解有关编译器、优化器和索引技术的更多信息。
我想在将页面引入缓冲池的请求(至少是读取)之间保持最大并行度,但我对如何在 .NET 中最好地实现这一点感到困惑。
以下是一些选项以及我在每个选项中遇到的问题:
使用
System.IO.FileStream
和BeginRead
方法但是,文件中的位置不是参数
BeginRead
, ,它是一个属性FileStream
(通过设置Seek
方法),所以我一次只能发出一个请求,并且必须在持续时间内锁定流。(或者我也这样?该文档不清楚如果我仅在Seek
和BeginRead
呼叫但在呼叫之前释放它EndRead
. 。有谁知道吗?)我知道该怎么做,我只是不确定这是最好的方法。似乎还有另一种方式,围绕
System.Threading.Overlapped
结构和 P\Invoke 到ReadFileEx
kernel32.dll 中的函数。不幸的是,缺乏示例,尤其是托管语言的示例。这条路线(如果它能够发挥作用的话)显然还涉及
ThreadPool.BindHandle
方法和线程池中的IO完成线程。我的印象是,这是在 Windows 下处理这种情况的认可方法,但我不理解它,也找不到对新手有帮助的文档入口点。还有别的事吗?
在评论中,雅各布建议创建一个新的
FileStream
每次飞行中读取。将整个文件读入内存。
如果数据库很小,这会起作用。代码库很小,而且还有很多其他效率低下的地方,但数据库本身却不是。我还想确保我正在做处理大型数据库所需的所有簿记(事实证明这是复杂性的很大一部分:分页,外部排序,...),我担心它可能很容易意外作弊。
编辑
澄清为什么我对解决方案 1 持怀疑态度:从 BeginRead 到 EndRead 一直持有一个锁意味着我需要阻止任何想要启动读取的人,因为另一个读取正在进行中。这感觉不对,因为启动新读取的线程可能(通常)能够在结果可用之前做更多的工作。(实际上,只是写这篇文章就让我想到了一个新的解决方案,我把它作为一个新的答案。)
解决方案
我们所做的就是在 C++/CLI 中围绕 I/O 完成端口、ReadFile 和 GetQueuedCompletion 状态编写一个小层,然后在操作完成时回调到 C# 中。我们选择此路线而不是 BeginRead 和 c# 异步操作模式,以提供对用于从文件(或套接字)读取的缓冲区的更多控制。与每次读取时在堆上分配新的 byte[] 的纯托管方法相比,这是一个相当大的性能提升。
另外,互联网上还有很多使用 IO Completion 端口的更完整的 C++ 示例
其他提示
我不确定我是否明白为什么选项 1 不适合您。请记住,您不能让两个不同的线程尝试同时使用同一个 FileStream - 这样做肯定会给您带来问题。BeginRead/EndRead 的目的是让您的代码在发生潜在昂贵的 IO 操作时继续执行,而不是启用对文件的某种多线程访问。
所以我建议你先寻找然后开始阅读。
如果您首先将资源(文件数据或其他内容)加载到内存中,然后跨线程共享它会怎么样?因为它是一个小数据库。- 你不会有那么多问题需要处理。
使用方法#1, 但
当请求到来时,获取锁A。使用它来保护待处理的读取请求队列。将其添加到队列并返回一些新的异步结果。如果这导致第一次添加到队列中,请在返回之前调用步骤 2。返回之前释放锁 A。
当读取完成(或由步骤 1 调用)时,获取锁 A。使用它来保护从队列中弹出读取请求。拿锁B。用它来保护
Seek
->BeginRead
->EndRead
顺序。释放锁 B.更新步骤 1 为此读取操作创建的异步结果。(由于读取操作已完成,请再次调用此操作。)
这解决了不会因为另一个读取正在进行而阻塞任何开始读取的线程的问题,但仍然对读取进行排序,以便文件流的当前位置不会混乱。