• 【免杀前置课——Windows编程】十四、异步IO——什么是异步IO、API定位问题、APC调用队列


    异步IO

    当我们读取一个文件时,一般情况下,线程是阻塞的,也就是说,当前线程在等待文件读取操作结束,这种方式叫同步IO
    Windows 在系统底层为用户实现了另外一种高效的机制,叫重叠I/0,又称作异步I/0。异步I/0提供了这样一种功能,当用户读取文件的时候,读取文件函数会立马返回结果不会阻塞线程,但是实际上文件并没有读取完,而是交给了系统底层自动去处理,这样文件的读取操作就不会阻塞住你的线程。但是这种引发了一个问题,我们如何才能知道文件已经读取完毕了呢? I

    异步I/0注意事项:

    一旦一个句柄是以异步I/0的方式打开的,那么:
    1、句柄变为可等待的对象,就是说,它具有了激发态和非激发态。
    2、文件指针这个东西就失效了,需要用overlapped结构体中的offset表示读取或写入的位置。

    在这里插入图片描述
    在这里插入图片描述

    定位问题

    异步IO存在问题,如果不止通过一个句柄进行读操作还有其他操作,则GetOverlappedResult函数无法定位到是哪一个操作。

    #include
    #include
    
    int 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异步IO
    	CHAR 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述
    解决无法精确定位是哪个API完成的问题。新问题是,到最后还是要调用WaitForSingleObject函数,如果操作过大还是会导致卡顿,无法流畅体验。

    #include
    #include
    
    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);
    	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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    总解决方案

    APC调用队列

    每个线程都维护了一个APC(异步过程调用)队列,队列中的每一项都是函数,当一个线程处于可警醒(闲暇)状态时,线程会遍历自己的APC队列并调用所有的函数,直到结束,再恢复执行。
    每当有一个异步IO请求完成的时候,ReadFileEx,就会像APC队列中添加相应的函数和对应的参数,添加的顺序和投递的顺序不一定相同,哪个先处理完就先投递哪个。

    通过ReadFileEx函数,让每一个异步IO完成后都向APC队列中加入函数及相应参数,来判断线程顺序的同时不使用WaitForSingleObject等待激发且抢占不影响流畅性。

    #include
    #include
    
    void 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
  • 相关阅读:
    电容如何能做升压?(电荷泵的工作原理及特性)
    释放机器人潜力,INDEMIND深耕底层技术
    Fabric2.2中的Raft共识模块源码分析
    【Day 3】Ajax + Vue 项目、路由 + Nginx
    Linux 下使用 Docker 安装 Redis
    Azure 开发者新闻快讯丨开发者8月大事记一览
    【Go】格式化字符串指令大全 && Redis常用命令
    Autosar诊断实战系列03-22服务读取DID数据的几种接口类型区别详解
    【LeetCode-中等题】46. 全排列
    线程和线程池
  • 原文地址:https://blog.csdn.net/m0_62783065/article/details/127872331