如何在c++中异步读取文件

How to read files asynchroneusly in c++?

本文关键字:异步 读取 文件 c++      更新时间:2023-10-16

我需要异步读取文件

string read(string path) {
            DWORD readenByte;
            int t;
            char* buffer = new char[512];
            HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, "read");
            OVERLAPPED overlap;
            overlap.hEvent = hEvent;
            HANDLE hFile = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
            if(!hFile) {
                Debug::error(GetLastError(), "fileAsync.cpp::read - ");
            }
            t = ReadFile(hFile, buffer, MAX_READ - 1, &readenByte, &overlap);
            if(!t) {
                Debug::error(GetLastError(), "fileAsync.cpp::read - ");
            }
            t = WaitForSingleObject(hEvent, 5000);
            if(t == WAIT_TIMEOUT) {
                Debug::error("fail to read - timeout, fileAsync.cpp::read");
            }
            buffer[readenByte] = '';
            string str = buffer;
            return str;
        }

我在ReadFile - 38中遇到错误:到达文件的末尾

如何在c++中使用winapi异步读取文件?

你的代码中有几个bug需要解决,有些会导致失败,有些会导致灾难性的失败。

  • 第一个错误导致你得到的错误代码:你有一个未初始化的OVERLAPPED结构,指示下面的ReadFile调用从存储在OffsetOffsetHigh成员中的随机文件位置读取。要解决这个问题,初始化数据:OVERLAPPED overlap = {0}; .

  • 接下来,您不打开文件以进行异步访问。随后从文件异步读取,您需要调用CreateFile传递FILE_FLAG_OVERLAPPEDdwFlagsAndAttributes。如果你不这样做,你就得花几个月的时间去寻找一个bug(参见如果你忘记在异步句柄上传递OVERLAPPED结构会发生什么?)。

  • ReadFile的文档解释说,lpNumberOfBytesRead参数不用于异步I/O,你应该传递NULL代替。这应该是显而易见的,因为在知道传输的字节数之前,异步ReadFile调用返回。要获取传输负载的大小,在异步I/O完成后调用GetOverlappedResult。

  • 下一个bug只会导致内存泄漏。您动态地分配buffer,但从不调用delete[] buffer;。要么删除缓冲区,要么分配一个具有自动存储时间的缓冲区(char buffer[MAX_READ] = {0};),或者使用c++容器(例如std::vector<char> buffer(MAX_READ);)。

  • 另一个错误是,当您尝试从buffer构造std::string时:您选择的构造函数不能处理嵌入的NUL字符。它只会截断你得到的。您需要调用带有显式长度参数的std::string构造函数。但即使这样,如果文件的字符编码与std::string不一致,您也可能以垃圾告终。

  • 最后,发出一个异步读,后面跟着WaitForSingleObject,本质上是一个同步读,不买任何东西。我假设这只是用于测试,而不是您的最终代码。在完成此操作时,请记住,只要异步读操作正在运行,OVERLAPPED结构就需要保持存活。

不能立即解决错误的其他建议:

  • 你正在传递一个std::string给你的read函数,在CreateFile调用中使用。Windows始终使用UTF-16LE编码,当使用Visual Studio(以及可能的其他Windows编译器)时,它映射到wchar_t/std::wstring。传递std::string/const char*有两个直接的缺点:

    1. 调用ANSI API导致字符串从MBCS转换为UTF-16(反之亦然)。这既不必要地浪费资源,又以非常微妙的方式失败,因为它依赖于当前的区域设置。
    2. 不是每个Unicode码点都可以用MBCS编码表示。这意味着,当使用MBCS字符编码时,某些文件无法打开。
  • 始终使用Unicode API (CreateFileW)和UTF-16字符串(std::wstring/wchar_t)。还可以在编译器的命令行中定义预处理器符号UNICODE(用于Windows API)和_UNICODE(用于CRT),以避免意外调用任何ANSI API。

  • 您正在创建一个只能通过其HANDLE值访问的事件对象,而不是通过其名称。您可以将NULL作为lpName参数传递给CreateEvent。这可以防止潜在的名称冲突,对于像"read"这样通用的名称,这一点尤为重要。

1)您需要在CreateFile调用的第6个参数(dwFlagsAndAttributes)中包含标志FILE_FLAG_OVERLAPPED。这就是为什么重叠读很可能失败的原因。

2) MAX_READ的值是多少?我希望它小于513,否则如果文件大于512字节,就会发生不好的事情。

3) ReadFile与重叠的结构指针不是NULL将给你错误码997 (ERROR_IO_PENDING),这是预期的,因此你不能在调用ReadFile后计算t

4)在异步操作的情况下,ReadFile函数不存储你在调用中传递的指针中读取的字节,你必须在操作完成后自己查询重叠的结果

这是一个小的工作片段,我希望你能从中构建:

#include <Windows.h>
#include <iostream>
#include <sstream>
class COverlappedCompletionEvent : public OVERLAPPED
{
public:
    COverlappedCompletionEvent() : m_hEvent(NULL)
    {
        m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        if (m_hEvent == NULL)
        {
            auto nError = GetLastError();
            std::stringstream ErrorStream;
            ErrorStream << "CreateEvent() failed with " << nError;
            throw std::runtime_error(ErrorStream.str());
        }
        ZeroMemory(this, sizeof(OVERLAPPED));
        hEvent = m_hEvent;
    }
    ~COverlappedCompletionEvent()
    {
        if (m_hEvent != NULL)
        {
            CloseHandle(m_hEvent);
        }
    }
private:
    HANDLE m_hEvent;
};
int main(int argc, char** argv)
{
    try
    {
        if (argc != 2)
        {
            std::stringstream ErrorStream;
            ErrorStream << "usage: " << argv[0] << " <filename>";
            throw std::runtime_error(ErrorStream.str());
        }
        COverlappedCompletionEvent OverlappedCompletionEvent;
        char pBuffer[512];
        auto hFile = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
        if (hFile == NULL)
        {
            auto nError = GetLastError();
            std::stringstream ErrorStream;
            ErrorStream << "CreateFileA() failed with " << nError;
            throw std::runtime_error(ErrorStream.str());
        }
        if (ReadFile(hFile, pBuffer, sizeof(pBuffer), nullptr, &OverlappedCompletionEvent) == FALSE)
        {
            auto nError = GetLastError();
            if (nError != ERROR_IO_PENDING)
            {
                std::stringstream ErrorStream;
                ErrorStream << "ReadFile() failed with " << nError;
                throw std::runtime_error(ErrorStream.str());
            }
        }
        ::WaitForSingleObject(OverlappedCompletionEvent.hEvent, INFINITE);
        DWORD nBytesRead = 0;
        if (GetOverlappedResult(hFile, &OverlappedCompletionEvent, &nBytesRead, FALSE))
        {
            std::cout << "Read " << nBytesRead << " bytes" << std::endl;
        }
        CloseHandle(hFile);
    }
    catch (const std::exception& Exception)
    {
        std::cout << Exception.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}