[英]Is this proper usage of the windows file API? (Multiple overlapped requests)
使用Windows文件API(特別是具有重疊IO的ReadFile)時遇到了一些奇怪的行為。
在某些情況下,GetOverlappedResult將成功地將數據讀取到提供的緩沖區中,但是將lpNumberOfBytesTransferred設置為零,而不是正確的讀取量。
僅當先前使用FILE_FLAG_NO_BUFFERING打開文件時,才在同一句柄上發出多個重疊的讀取請求時,才似乎發生這種情況。
這是說明問題的完整代碼示例...
#include <Windows.h>
#include <string>
#include <iostream>
const int PageSize = 4096;
const int BufferSize = PageSize * 4;
struct OperationSlot
{
OVERLAPPED state;
unsigned char* buffer;
};
bool enableFail;
bool ReadTest(std::string filename, DWORD flags, int queueSize)
{
bool result = true;
if (enableFail)
{
HANDLE temp = CreateFile(filename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);
CloseHandle(temp);
}
OperationSlot* slots = new OperationSlot[queueSize];
for (int i = 0; i < queueSize; ++i)
{
slots[i].buffer = (unsigned char*)_aligned_malloc(BufferSize, PageSize);
ZeroMemory(slots[i].buffer, BufferSize);
}
HANDLE file = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, flags, NULL);
HANDLE controlFile = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
unsigned char* controlBuffer = new unsigned char[BufferSize];
// Start async read operations...
for (int i = 0; i < queueSize; ++i)
{
ZeroMemory(&slots[i].state, sizeof(OVERLAPPED));
slots[i].state.Offset = i * BufferSize;
bool ok = ReadFile(file, slots[i].buffer, BufferSize, NULL, &slots[i].state);
if (!ok)
{
DWORD err = GetLastError();
if (err != ERROR_IO_PENDING)
{
std::cout << "ReadFile set error code " << err << std::endl;
}
}
}
int readId = 0;
while (true)
{
OperationSlot& active_slot = slots[readId % queueSize];
DWORD bytes = 0;
bool ok = GetOverlappedResult(file, &active_slot.state, &bytes, true);
DWORD err = GetLastError();
DWORD controlBytes = 0;
ReadFile(controlFile, controlBuffer, BufferSize, &controlBytes, NULL);
bool dataok = memcmp(active_slot.buffer, controlBuffer, controlBytes) == 0;
if (!dataok)
std::cout << "Data mismatch." << std::endl;
if (bytes != controlBytes)
{
std::cout << "Error with QueueSize (" << queueSize << ") and flags: ";
if (flags & FILE_FLAG_OVERLAPPED)
{
std::cout << "FILE_FLAG_OVERLAPPED";
}
if (flags & FILE_FLAG_NO_BUFFERING)
{
std::cout << " | FILE_FLAG_NO_BUFFERING";
}
if (flags & FILE_FLAG_SEQUENTIAL_SCAN)
{
std::cout << " | FILE_FLAG_SEQUENTIAL_SCAN";
}
std::cout << std::endl;
std::cout << "Read size error, expected " << controlBytes << ", got " << bytes << std::endl;
std::cout << "GetOverlappedResult returned " << ok << " with error code " << err << std::endl;
result = false;
}
if (controlBytes < BufferSize)
break;
ZeroMemory(&active_slot.state, sizeof(OVERLAPPED));
active_slot.state.Offset = (readId + queueSize) * BufferSize;
ReadFile(file, active_slot.buffer, BufferSize, NULL, &active_slot.state);
++readId;
}
CloseHandle(file);
CloseHandle(controlFile);
delete[] controlBuffer;
for (int i = 0; i < queueSize; ++i)
{
_aligned_free(slots[i].buffer);
}
delete[] slots;
return !result;
}
int main()
{
enableFail = false;
int totalfail = 0;
std::cout << "Testing without fail." << std::endl;
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 4);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);
std::cout << totalfail << " calls failed." << std::endl;
enableFail = true;
totalfail = 0;
std::cout << "Testing with fail enabled." << std::endl;
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 4);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);
std::cout << totalfail << " calls failed." << std::endl;
system("pause");
return 0;
}
在我的系統上,這導致4個調用“失敗”。 (“隊列大小”大於一個的隊列。)
發生的情況是,重疊的版本報告它僅讀取0個字節,而“普通”文件句柄讀取為20個字節。數據正確。 用正確的數據填充正確的緩沖區,只有報告的傳輸數據量是錯誤的...
只有在打開文件之前立即使用FILE_FLAG_NO_BUFFERING打開和關閉文件時,才會發生這種情況。
為什么以前接觸文件會導致后續訪問的行為與此不同?
我是在做不被支持的事情,還是API無法按照預期的方式運行? (或者我忽略的代碼中可能有錯誤...?)
編輯:看來我被誤認為我在正確使用API,因為它在大多數情況下都能正常工作。 正確的方法是為每個重疊的結構指定唯一的事件,如公認的答案中所指出的。 在給每個重疊的請求后,它是自己的事件,它可以一致地工作。
您沒有為OVERLAPPED
結構提供唯一的事件,因此,所有要等待的GetOverlappedResult()
都是文件句柄-並且在有多個請求未完成的情況下,不能保證您所請求的請求實際上將是文件完成時的請求手柄發出信號。
每個OVERLAPPED
結構都應將其hEvent
成員初始化為使用CreateEvent()
創建的新事件句柄。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.