WriteFileEx function
SleepEx
1)异步写函数WriteFileEx()仅是发出写请求,然后就返回。该函数不使用LPOVERLAPPED中的HANDLE hEvent参数,用户可以在里面放一些自己的数据。
2)给每个WriteFileEx()准备一个POVERLAPPED对象,且buffer在IO完成前不能修改。
3)CompletionRoutine()和WriteFileEx()在同一个线程里被调用,通知用户I/O操作完成了。
写磁盘真正完成后,系统在调用WriteFileEx的线程里调用CompletionRoutine函数,因此线程需要用SleepEx或WaitSingleObject、WaitMultipleObjects来等待写磁盘完成。(可以发送多次写请求,然后再统一等待)。
WriteFileEx
异步写文件或I/O设备。该函数用异步的方式通知完成状态,调用指定的完成函数CompletionRoutine,且calling线程在alterable wait state。
hFile
文件句柄,用CreateFile获取该句柄时必须用FILE_FLAG_OVERLAPPED 标记,和GENERIC_WRITE标记。
lpBuffer
缓冲区,包含需要写入到文件中的数据。
nNumberOfBytesToWrite
需要写入到文件中的数据量。
lpOverlapped
指向OVERLAPPED结构的指针,该数据在整个异步写操作中都会用到。因此该数据在写操作真正结束前,不能变的无效。
写文件时,用overlapped对象的Offset和OffsetHigh参数指定字节偏移量。
如果要在文件的最后面写数据,把Offset和OffsetHigh都设置成0xFFFF_FFFF即可。
该函数不使用OVERLAPPED的hEvent对象,用户可以留作自用。
WriteFileEx使用OVERLPPED的Internal和InernalHigh变量,用户不应该改变他们。
lpCompletionRoutine
指向一个回调函数,当写操作完成时会调用该函数。calling thread is in an alertable wait state.
返回值
函数成功则返回非零值。如果函数失败,则返回0,用GetLastError获取错误码。
如果WriteFileEx成功,calling thread有一个未完成的异步I/O操作:写数据到文件。当该I/O完成时,calling thread在alertable wait state,系统调用CompletionRoutine函数,且等待结束(等待码时WAIT_IO_COMPLETION).
如果函数成功且写文件操作完成,但是calling thread不在alertable wait state,系统会把completionRoutine的调用排入队列,直到the calling thread进入alertable wait state.
注意事项
当使用WriteFileEx时,即使返回成功也要调用GetLastError。例如当调用WriteFileEx时产生buffer溢出,函数会返回成功,但是GetLastError会用ERROR_MORE_DATA报告错误。
当有太多未完成的异步I/O请求时,WriteFileEx可能会返回错误,GetLastError会返回ERROR_INVALID_USER_BUFFER或ERROR_NOT_ENOUGH_MEMORY.
想要取消所有未完成的异步IO,使用:
CanelIo:仅取消该线程对file handle的操作;
CanelIoEx:取消所有线程对该file handle的操作。
取消的IO返回时有错误码ERROR_OPERATION_ABORTED.
在写操作完成前,不要修改buffer里的内容,否则可能会出错。
application使用WaitForSingleObjectEx, WaitForMultipleObjectsEx, MsgWaitForMultipleObjectsEx, SignalObjectAndWait, and SleepEx 函数来进入alertable wait state.
#include
#include
#include
const int MAX_IO = 100000;
const int BUF_SIZE = 1024;
char bufArray[MAX_IO][BUF_SIZE];
OVERLAPPED gOverLap[MAX_IO];
int gCompletionNum = 0;
int gTotalBytesWritten = 0;
HANDLE gFileHandle;
//完成函数
void WINAPI CompletionRoutine(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped
) {
gCompletionNum++;
gTotalBytesWritten += dwNumberOfBytesTransfered;
//打印错误码
if (dwErrorCode != ERROR_SUCCESS) {
printf("Error Code in CompletioRoutine is: %d\n.", dwErrorCode);
}
if (gCompletionNum == 1) {
auto threadId = std::this_thread::get_id();
printf("Thread Id of CompletionRoutine is :%d.\n", threadId);
}
return;
}
//线程函数,写磁盘
void ThreadFunc(void)
{
bool result = TRUE;
DWORD errCode = 0;
DWORD status = 0;
int wrCnt = 0;
auto threadId = std::this_thread::get_id();
printf("Thread Id of CompletionRoutine is :%d.\n", threadId);
for (int i = 0; i < MAX_IO; i++)
{
//异步写
result = WriteFileEx(gFileHandle,
bufArray[i],
BUF_SIZE,
&gOverLap[i],
CompletionRoutine);
//判断是否成功
if (FALSE == result)
{
errCode = GetLastError();
printf("Error Code at WriteFileEx is: %d.\n", errCode);
}
else {
wrCnt++;
}
}
//等待写磁盘结束
int i = 0;
while (gCompletionNum != wrCnt) {
status = SleepEx(INFINITE, TRUE);
i++;
printf("ThreadFunc = %d, SleepEx is called %d times.\n", gCompletionNum,i);
}
printf("gTotalBytesWritten is %d.\n", gTotalBytesWritten);
printf("WriteThread is finished.\n");
}
int main()
{
DWORD errCode = 0;
//初始化数组
for (int i = 0; i < MAX_IO; i++)
{
for (int j = 0; j < BUF_SIZE; j++)
{
bufArray[i][j] = i * BUF_SIZE + j;
}
}
//初始化overlapped
for (int i = 0; i < MAX_IO; i++) {
gOverLap[i].Offset = 0xFFFFFFFF;
gOverLap[i].OffsetHigh = 0xFFFFFFFF;
}
//打开文件
printf("Begin write thread...\n");
gFileHandle = CreateFile(L"E:\\Async.dat",
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
0);
if (INVALID_HANDLE_VALUE == gFileHandle) {
errCode = GetLastError();
printf("Failed to open file. ErrorCode = %d.\n", errCode);
getchar();
}
//写文件
std::thread t1(ThreadFunc);
//等待文件写完
t1.join();
//关闭文件
CloseHandle(gFileHandle);
getchar();
}