IOCP ReadFile 始终阻塞直到读取完成
IOCP ReadFile always blocks until read completion
这是使用 iocp 读取文件的示例源。
应该立即返回,因为它在调用ReadFile时进行了异步调用,而ReadFile似乎是同步工作的。
有什么问题?
他的测试环境是visual studio2017 enterprise,windwos 10,
windows sdk版本为10.0.17763.0.
#define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1)
const int BUFFERSIZE = 1024 * 1024 * 400;
BYTE ReadBuffer[BUFFERSIZE] = { 0 };
DWORD WINAPI WaitQueue(LPVOID lpParam)
{
auto hIocp = (HANDLE)lpParam;
// WAIT COMPLETION QUEUE
DWORD numberOfBytes;
ULONG_PTR val;
LPOVERLAPPED ov = { 0 };
for (;;)
{
BOOL bSuccess = GetQueuedCompletionStatus(hIocp, &numberOfBytes, (PULONG_PTR)&val, &ov, INFINITE);
SYSTEMTIME dequeTime;
GetSystemTime(&dequeTime);
Sleep(1000);
printf("dequeue time %dsec %dmilli", dequeTime.wSecond, dequeTime.wMilliseconds);
}
}
DWORD WINAPI ReadFileThread(LPVOID lpParam)
{
Sleep(3000);
auto hIocp = (HANDLE)lpParam;
// CREATE FILE HANDLE
auto fileName = "e:\test.msi";
auto hFile = CreateFile(fileName,
FILE_READ_DATA,
FILE_SHARE_READ,
0,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
0);
if (hFile == INVALID_HANDLE_VALUE)
{
std::wcout << L"create file fail - " << fileName << std::endl;
return 0;
}
// REGIST FILE HANDLE TO IOCP
if (hIocp != CreateIoCompletionPort(hFile, hIocp, 0, 2))
{
auto err = GetLastError();
std::cout << "add file handle fail:" << err << " - file handle:" << hIocp << std::endl;
CloseHandle(hFile);
CloseHandle(hIocp);
return 0;
}
// READ FILE
OVERLAPPED ol = { 0 };
SYSTEMTIME startTime;
GetSystemTime(&startTime);
if (FALSE == ReadFile(hFile, ReadBuffer, _countof(ReadBuffer), 0, &ol))
{
if (GetLastError() != ERROR_IO_PENDING)
{
printf("Terminal failure: Unable to read from file.\n GetLastError=%08x\n", GetLastError());
CloseHandle(hFile);
return -1;
}
}
DWORD d;
GetOverlappedResult(hFile, &ol, &d, true);
SYSTEMTIME endTime;
GetSystemTime(&endTime);
printf("start time %dsec %dmilli", startTime.wSecond, startTime.wMilliseconds);
printf("end time %dsec %dmilli", endTime.wSecond, endTime.wMilliseconds);
}
int main()
{
// CREATE ICOP
auto hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1);
if (hIocp == NULL)
{
auto err = GetLastError();
std::cout << "Create IOCP failed. error:" << err << std::endl;
return 0;
}
// CREATE READ THREAD
CreateThread(
NULL,
0,
ReadFileThread,
hIocp,
0,
nullptr
);
// CREATE WAIT DEQUEUE THREAD
CreateThread(
NULL,
0,
WaitQueue,
hIocp,
0,
nullptr
);
while (true)
{
}
return 0;
}
首先这里的iocp是绝对不相关的。您尝试立即或阻止异步文件句柄 return 上的 I/O 操作(在您的情况下读取)。 iocp 这里有什么关系?将 iocp 绑定到文件是获得通知的唯一方式,当 I/O 完成时。但这绝对不影响 I/O 自己阻止或 return 立即。我们可以在这里使用任何通知方式。 (apc、iocp 或事件)。为了您的测试目的,最简单的使用事件。
那么让我们看看 - 您如何测试 - 在您的代码中是读取块还是 return?你根本不测试这个。在 ReadFile
return 之后测试此需求 - 操作是否完成。 I/O 操作 (ReadFile) 是异步完成的 - 如果 api 已经调用 return 控制给你,但是 OVERLAPPED
(IO_STATUS_BLOCK
) 还没有被系统更新,这意味着 I/O 仍未完成。 OVERLAPPED
是否已更新我们可以直接检查(OVERLAPPED
结构的 Internal
成员是 不是 STATUS_PENDING
)或通过调用 GetOverlappedResult
bWait 设置为 FALSE
If this parameter is FALSE
and the operation is still pending, the
function returns FALSE
and the GetLastError
function returns
ERROR_IO_INCOMPLETE
.
所以如果接下来的 4 个条件为真,我们可以说 ReadFile
异步完成:
ReadFile
return FALSE
GetLastError()
return ERROR_IO_PENDING
GetOverlappedResult(.., FALSE)
return FALSE
GetLastError()
return ERROR_IO_INCOMPLETE
你没有在自己的代码中检查这个。相反,你要等到 io 操作通过 GetOverlappedResult(.., true)
完全完成,然后花点时间。这样做有什么意义?
实际测试代码:
void tt(PCWSTR FileName)
{
HANDLE hFile = CreateFile(FileName, FILE_GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
OVERLAPPED ov {};
if (ov.hEvent = CreateEvent(0, 0, 0, 0))
{
char buf[1024];
if (ReadFile(hFile, buf, sizeof(buf), 0, &ov))
{
DbgPrint("sync(#1)\n");
}
else
{
switch (GetLastError())
{
case ERROR_IO_PENDING:
ULONG n;
if (GetOverlappedResult(hFile, &ov, &n, FALSE))
{
DbgPrint("sync(#2)\n");
}
else
{
switch (GetLastError())
{
case ERROR_IO_INCOMPLETE:
DbgPrint("async\n");
if (!GetOverlappedResult(hFile, &ov, &n, TRUE))
{
__debugbreak();
}
break;
default: __debugbreak();
}
}
break;
default: __debugbreak();
}
}
CloseHandle(ov.hEvent);
}
CloseHandle(hFile);
}
}
请注意,读取完成的结果(同步或非同步)取决于缓存中的文件数据。如果你为文件调用它,之前没有读取它(所以数据不在缓存中)可能 api 打印 "async",但是如果你再次为同一个文件调用这个函数 - 你下次会更快(几乎 100%)将查看 "sync(#2)"
这是使用 iocp 读取文件的示例源。
应该立即返回,因为它在调用ReadFile时进行了异步调用,而ReadFile似乎是同步工作的。
有什么问题?
他的测试环境是visual studio2017 enterprise,windwos 10, windows sdk版本为10.0.17763.0.
#define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1)
const int BUFFERSIZE = 1024 * 1024 * 400;
BYTE ReadBuffer[BUFFERSIZE] = { 0 };
DWORD WINAPI WaitQueue(LPVOID lpParam)
{
auto hIocp = (HANDLE)lpParam;
// WAIT COMPLETION QUEUE
DWORD numberOfBytes;
ULONG_PTR val;
LPOVERLAPPED ov = { 0 };
for (;;)
{
BOOL bSuccess = GetQueuedCompletionStatus(hIocp, &numberOfBytes, (PULONG_PTR)&val, &ov, INFINITE);
SYSTEMTIME dequeTime;
GetSystemTime(&dequeTime);
Sleep(1000);
printf("dequeue time %dsec %dmilli", dequeTime.wSecond, dequeTime.wMilliseconds);
}
}
DWORD WINAPI ReadFileThread(LPVOID lpParam)
{
Sleep(3000);
auto hIocp = (HANDLE)lpParam;
// CREATE FILE HANDLE
auto fileName = "e:\test.msi";
auto hFile = CreateFile(fileName,
FILE_READ_DATA,
FILE_SHARE_READ,
0,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
0);
if (hFile == INVALID_HANDLE_VALUE)
{
std::wcout << L"create file fail - " << fileName << std::endl;
return 0;
}
// REGIST FILE HANDLE TO IOCP
if (hIocp != CreateIoCompletionPort(hFile, hIocp, 0, 2))
{
auto err = GetLastError();
std::cout << "add file handle fail:" << err << " - file handle:" << hIocp << std::endl;
CloseHandle(hFile);
CloseHandle(hIocp);
return 0;
}
// READ FILE
OVERLAPPED ol = { 0 };
SYSTEMTIME startTime;
GetSystemTime(&startTime);
if (FALSE == ReadFile(hFile, ReadBuffer, _countof(ReadBuffer), 0, &ol))
{
if (GetLastError() != ERROR_IO_PENDING)
{
printf("Terminal failure: Unable to read from file.\n GetLastError=%08x\n", GetLastError());
CloseHandle(hFile);
return -1;
}
}
DWORD d;
GetOverlappedResult(hFile, &ol, &d, true);
SYSTEMTIME endTime;
GetSystemTime(&endTime);
printf("start time %dsec %dmilli", startTime.wSecond, startTime.wMilliseconds);
printf("end time %dsec %dmilli", endTime.wSecond, endTime.wMilliseconds);
}
int main()
{
// CREATE ICOP
auto hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1);
if (hIocp == NULL)
{
auto err = GetLastError();
std::cout << "Create IOCP failed. error:" << err << std::endl;
return 0;
}
// CREATE READ THREAD
CreateThread(
NULL,
0,
ReadFileThread,
hIocp,
0,
nullptr
);
// CREATE WAIT DEQUEUE THREAD
CreateThread(
NULL,
0,
WaitQueue,
hIocp,
0,
nullptr
);
while (true)
{
}
return 0;
}
首先这里的iocp是绝对不相关的。您尝试立即或阻止异步文件句柄 return 上的 I/O 操作(在您的情况下读取)。 iocp 这里有什么关系?将 iocp 绑定到文件是获得通知的唯一方式,当 I/O 完成时。但这绝对不影响 I/O 自己阻止或 return 立即。我们可以在这里使用任何通知方式。 (apc、iocp 或事件)。为了您的测试目的,最简单的使用事件。
那么让我们看看 - 您如何测试 - 在您的代码中是读取块还是 return?你根本不测试这个。在 ReadFile
return 之后测试此需求 - 操作是否完成。 I/O 操作 (ReadFile) 是异步完成的 - 如果 api 已经调用 return 控制给你,但是 OVERLAPPED
(IO_STATUS_BLOCK
) 还没有被系统更新,这意味着 I/O 仍未完成。 OVERLAPPED
是否已更新我们可以直接检查(OVERLAPPED
结构的 Internal
成员是 不是 STATUS_PENDING
)或通过调用 GetOverlappedResult
bWait 设置为 FALSE
If this parameter is
FALSE
and the operation is still pending, the function returnsFALSE
and theGetLastError
function returnsERROR_IO_INCOMPLETE
.
所以如果接下来的 4 个条件为真,我们可以说 ReadFile
异步完成:
ReadFile
returnFALSE
GetLastError()
returnERROR_IO_PENDING
GetOverlappedResult(.., FALSE)
returnFALSE
GetLastError()
returnERROR_IO_INCOMPLETE
你没有在自己的代码中检查这个。相反,你要等到 io 操作通过 GetOverlappedResult(.., true)
完全完成,然后花点时间。这样做有什么意义?
实际测试代码:
void tt(PCWSTR FileName)
{
HANDLE hFile = CreateFile(FileName, FILE_GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
OVERLAPPED ov {};
if (ov.hEvent = CreateEvent(0, 0, 0, 0))
{
char buf[1024];
if (ReadFile(hFile, buf, sizeof(buf), 0, &ov))
{
DbgPrint("sync(#1)\n");
}
else
{
switch (GetLastError())
{
case ERROR_IO_PENDING:
ULONG n;
if (GetOverlappedResult(hFile, &ov, &n, FALSE))
{
DbgPrint("sync(#2)\n");
}
else
{
switch (GetLastError())
{
case ERROR_IO_INCOMPLETE:
DbgPrint("async\n");
if (!GetOverlappedResult(hFile, &ov, &n, TRUE))
{
__debugbreak();
}
break;
default: __debugbreak();
}
}
break;
default: __debugbreak();
}
}
CloseHandle(ov.hEvent);
}
CloseHandle(hFile);
}
}
请注意,读取完成的结果(同步或非同步)取决于缓存中的文件数据。如果你为文件调用它,之前没有读取它(所以数据不在缓存中)可能 api 打印 "async",但是如果你再次为同一个文件调用这个函数 - 你下次会更快(几乎 100%)将查看 "sync(#2)"