【问题标题】:Creating an asynchronous "ReadFileMany()" on top of ReadFile()在 ReadFile() 之上创建异步“ReadFileMany()”
【发布时间】:2013-03-01 21:30:05
【问题描述】:

我正在尝试创建一个函数,ReadFileMany,它模仿 ReadFile 的接口,它给出了一个 list 的(偏移量,长度)对来读取,并且异步读取文件的所有部分。

我在ReadFileMany 内部执行此操作,使用RegisterWaitForSingleObject 指示线程池线程等待I/O 完成,以便它可以再次调用ReadFile 来读取文件的下一部分,等等

我遇到的问题是我似乎无法模仿 ReadFile 的某种行为。
具体来说,文件句柄本身可以像事件一样使用,不需要事件句柄:

OVERLAPPED ov = {0};
DWORD nw;
if (ReadFile(hFile, buf, buf_size, &nw, &ov))
{
    if (WaitForSingleObject(hFile, INFINITE) == WAIT_OBJECT_0)
    {
        ...
    }
}

当然,如果用户在等待一个文件句柄,他可能会收到一个中间读取已完成的通知,而不是最终读取。

所以,在ReadFileMany 中,我别无选择,只能将hEvent 参数传递给ReadFile除了最后一部分

问题是,有没有办法让用户在读取所有部分后仍然等待文件句柄发出信号?

起初答案似乎很明显:在读取 last 部分时避免传递事件句柄!
如果读取成功,则效果很好,但如果出现错误,则效果不佳。如果 ReadFile 在上次读取时突然返回错误,我将需要手动将文件句柄设置为信号状态,以便让读者从对 @987654334 的潜在调用中醒来@。

但似乎没有办法在不执行实际 I/O 的情况下将 文件句柄 设置为信号状态...所以这就是我卡住的地方:我有什么办法可以编写这个函数,使其在外部表现得像ReadFile,还是不可能正确地做到这一点?

【问题讨论】:

  • 您从哪里得到可以将文件句柄传递给WaitForSingleObject() 的想法?文件句柄不是可等待对象,因此您不能等待它。
  • ReadFileScatter 你想写什么?
  • @Damon:不;实际上恰恰相反。 ReadFileScatter 中的“scatter”指的是内存缓冲区(内存中的分散页面),而ReadFileMemory 中的“many”指的是文件的不同部分。
  • @RemyLebeau:是的,你确实可以。 (试试看。)显然ReadFile 文档似乎避免提及它,但是如果您查看ZwReadFile,您会发现这是完全有效的,并且文件句柄可以像事件句柄一样发出信号: “调用线程可以通过等待文件句柄来同步读取操作的完成。每次在句柄上发出的 I/O 操作完成时,都会向句柄发出信号。”
  • @Mehrdad:根据该文档,它仅在使用 SYNCHRONIZE 访问权限打开文件且一次只执行一个 I/O 操作时才有效。应使用ZwWaitForSingleObject() 完成等待。 CreateFile()ReadFile()WaitFor...() 文档均未提及有关文件句柄被支持为可等待对象的任何内容,因此我真的不会依赖这种行为。 ZwReadFile() 是比 ReadFile() 更低级别的 API,因此不要将更低级别和更高级别的 API 语义混合在一起。

标签: c winapi visual-c++ readfile


【解决方案1】:

假设您正在尝试读取单个文件的多个部分,我将简单地分配一个 OVERLAPPED 结构数组,一个用于文件的每个请求部分,一次启动所有 ReadFile() 调用,然后使用WaitForMultipleObjects() 等待所有 I/O 完成,例如:

struct sReadInfo
{
    OVERLAPPED ov;
    LPVOID pBuffer;
    DWORD dwNumBytes;
    DWORD dwNumBytesRead;
    bool bPending;

    sReadInfo()
    {
        memset(&ov, 0, sizeof(OVERLAPPED));
        pBuffer = NULL;
        dwNumBytes = 0;
        dwNumBytesRead = 0;
        bPending = false;

        ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (!ov.hEvent) throw std::exception();
    }

    ~sReadInfo()
    {
        CloseHandle(hEvent);
    }
};

.

bool error = false;

try
{
    std::vector<sReadInfo> ri(numSections);

    std::vector<HANDLE> h;
    h.reserve(numSections);

    for(int i = 0; i < numSections; ++i)
    {
        ULARGE_INTEGER ul;
        ul.QuadPart = ...; // desired file offset to read from

        sReadInfo *r = &ri[i];

        r->ov.Offset = ul.LowPart;
        r->ov.OffsetHigh = ul.HighPart;

        r->pBuffer = ...; // desired buffer to read into
        r->dwNumBytes = ...; // desired number of bytes to read

        if (!ReadFile(hFile, r->pBuffer, r->dwNumBytes, &r->dwNumBytesRead, &r->ov))
        {
            if (GetLastError() != ERROR_IO_PENDING)
                throw std::exception();

            r->bPending = true;
            h.push_back(r->ov.hEvent);
        }
    }

    if (!h.empty())
    {
        if (WaitForMultipleObjects(h.size(), &h[0], TRUE, INFINITE) != WAIT_OBJECT_0)
            throw std::exception();
    }

    for (int i = 0; i < numSections; ++i)
    {
        sReadInfo *r = &ri[i];

        if (r->bPending)
            GetOverlappedResult(hFile, &r->ov, &r->dwNumBytesRead, FALSE);

        // ...
    }
}
catch (const std::exception &)
{
    CancelIo(hFile);
    return false;
}

return true;

【讨论】:

  • 有趣...这不会导致磁盘大量抖动吗?用户如何知道所有读取已完成,而不仅仅是其中一个?似乎我们仍然遇到和以前一样的问题。
  • 它仍然是重叠的 I/O,因此 Windows 可以根据需要安排实际的磁盘活动。如果您仔细观察,我将 WaitForMultipleObjects()bWaitAll 参数设置为 TRUE,因此它不会退出,直到 所有 请求的读取完成。
  • 看起来你完全错过了这个问题,这与等待 file 句柄有关。您正在等待事件句柄,这已经是我在问题中提到的内容,但它没有为问题提供与我所询问的相同的接口。仔细重新阅读问题以了解问题的真正含义。
猜你喜欢
  • 1970-01-01
  • 2015-08-19
  • 1970-01-01
  • 2021-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-05
  • 2015-11-02
相关资源
最近更新 更多