当我们读取一个文件时,一般情况下,线程是阻塞的,也就是说,当前线程在等待文件读取操作结束,这种方式叫同步IO。
Windows 在系统底层为用户实现了另外一种高效的机制,叫重叠I/0,又称作异步I/0
。异步I/0提供了这样一种功能,当用户读取文件的时候,读取文件函数会立马返回结果不会阻塞线程,但是实际上文件并没有读取完,而是交给了系统底层自动去处理,这样文件的读取操作就不会阻塞住你的线程。但是这种引发了一个问题,我们如何才能知道文件已经读取完毕了呢? I
一旦一个句柄是以异步I/0的方式打开的,那么:
1、句柄变为可等待的对象,就是说,它具有了激发态和非激发态。
2、文件指针这个东西就失效了,需要用overlapped结构体中的offset表示读取或写入的位置。
异步IO存在问题,如果不止通过一个句柄进行读操作还有其他操作,则GetOverlappedResult函数无法定位到是哪一个操作。
#include
#includeint main()
{//用异步IO方式打开一个文件HANDLE hFile = CreateFileW(L"D:\\code\\VisualStudio2022\\异步Io\\异步Io\\test.txt", GENERIC_ALL, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);//FILE_FLAG_OVERLAPPED异步IOCHAR buff[0X100]{ 0 };OVERLAPPED overlapped{ 0 };//读取文件内容ReadFile(hFile, buff, 0X100, NULL, &overlapped);DWORD numberOfBytes = 0;WaitForSingleObject(hFile, -1);GetOverlappedResult(hFile, &overlapped, &numberOfBytes, FALSE);printf("文件内容:%s\n", buff);printf("实际完成IO数:%d\n", numberOfBytes);CloseHandle(hFile);return 0;
}
解决无法精确定位是哪个API完成的问题。新问题是,到最后还是要调用WaitForSingleObject函数,如果操作过大还是会导致卡顿,无法流畅体验。
#include
#includeint main()
{HANDLE hFile = CreateFile(L"D:\\code\\VisualStudio2022\\异步Io\\异步Io\\test.txt", GENERIC_ALL, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);OVERLAPPED overlapped{ 0 };OVERLAPPED overlapped1{ 0 };overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);overlapped1.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);char buff[0X100]{ 0 };char buff1[0X100]{ 0 };ReadFile(hFile, buff, 10, NULL, &overlapped);ReadFile(hFile, buff1, 20, NULL, &overlapped1);//精确地判断哪个结束WaitForSingleObject(overlapped.hEvent, -1);WaitForSingleObject(overlapped1.hEvent, -1);printf("%s\n", buff);printf("%s\n", buff1);return 0;
}
每个线程都维护了一个APC(异步过程调用)队列,队列中的每一项都是函数,当一个线程处于可警醒(闲暇)状态时,线程会遍历自己的APC队列并调用所有的函数,直到结束,再恢复执行。
每当有一个异步IO请求完成的时候,ReadFileEx,就会像APC队列中添加相应的函数和对应的参数,添加的顺序和投递的顺序不一定相同,哪个先处理完就先投递哪个。
通过ReadFileEx函数,让每一个异步IO完成后都向APC队列中加入函数及相应参数,来判断线程顺序的同时不使用WaitForSingleObject等待激发且抢占不影响流畅性。
#include
#includevoid WINAPI overLappedCompletionProc(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
{if (lpOverlapped->hEvent == (HANDLE)0X100){printf("1完成了\n");}else {printf("2完成了\n");}
}int main()
{HANDLE hFile = CreateFile(L"D:\\code\\VisualStudio2022\\异步Io\\异步Io\\test.txt", GENERIC_ALL, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);OVERLAPPED overlapped{ 0 };OVERLAPPED overlapped1{ 0 };//overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);//overlapped1.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);overlapped.hEvent = (HANDLE)0X100;overlapped1.hEvent = (HANDLE)0X200;char buff[0X100]{ 0 };char buff1[0X100]{ 0 };//当有一个异步IO请求完成后,就会向APC队列中添加相应的函数和对应的参数ReadFileEx(hFile, buff, 10, &overlapped, overLappedCompletionProc);ReadFileEx(hFile, buff1, 20, &overlapped1, overLappedCompletionProc);//当进程处于激发态时触发,通过Sleep让进程处于激发态SleepEx(0, TRUE);//ReadFile(hFile, buff, 10, NULL, &overlapped);//ReadFile(hFile, buff1, 20, NULL, &overlapped1);//精确地判断哪个结束//WaitForSingleObject(overlapped.hEvent, -1);//WaitForSingleObject(overlapped1.hEvent, -1);printf("%s\n", buff);printf("%s\n", buff1);return 0;
}