windows 异步IO操作的几种实现方法

异步IO

当我们读取一个文件时,一般情况下,线程是阻塞的,也就是说,当前线程在等待文件读取操作结束,这种方式叫同步IO

Windows在系统底层为用户实现了另外一种高效的机制,叫重叠I/O,又称作异步I/O

异步I/O提供了这样一种功能当用户读取文件的时候,读取文件函数会立马返回结果不会阻塞线程,但是实际上文件并没有读取完,而是交给了系统底层自动去处理,这样文件的读取操作就不会阻塞住你的线程。

但是这种引发了一个问题,我们如何才能知道文件已经读取完毕了呢?

异步I/O注意事项:
一旦一个句柄是以异步I/O的方式打开的,那么:
1、句柄变为可等待的对象,就是说,它具有了有信号状态无信号状态
2、用overlapped结构体中的offset表示读取或者写入的位置

typedef struct _OVERLAPPED {
        ULONG_PTR Internal;		//异步IO操作状态,是否成功。
        ULONG_PTR InternalHigh;	//操作了多少个字节。 
        union {					//文件偏移。
            struct {
                DWORD Offset;
                DWORD OffsetHigh;
            } DUMMYSTRUCTNAME;
            PVOID Pointer;
        } DUMMYUNIONNAME;
        HANDLE    hEvent;		//事件对象
    } OVERLAPPED, * LPOVERLAPPED;

操作文件API函数:

CreateFile	创建/打开文件
ReadFile	读取文件
WriteFile	写入文件
WriteFileEX	
ReadFile	
OVERLAPPED	重叠I/O结构体

设备内核对象

Windows将设备句柄看作可同步的对象,即它可以处于有信号或处于无信号状态,当创建设备句柄、以异步的方式发送IO请求时,该句柄处于无信号状态,当异步IO完成之后,该句柄受信,通过WaitForSingleobject或WaitForMultipleObjects函数可以判断设备操作何时完成。

#include <Windows.h>
#include <iostream>
int main()
{
	//打开一个文件
	HANDLE file1 = CreateFile(
		L"test.txt",
		GENERIC_READ,
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		FILE_FLAG_OVERLAPPED,
		NULL
	);
	//OVERLAPPED结构体专门用来存储异步IO的内容
	OVERLAPPED overlapped{ 0 };
	CHAR buff[0x100]{ 0 };
	//读取的字节要设置为NULL
	ReadFile(file1,
		buff,
		0x100,
		NULL,
		&overlapped
	);
	/*
	........在此处可以添加其他代码..........
	*/
	//等待file1是否已经读取完,是否是有信号状态(表示线程已经结束,即读取完毕)
	WaitForSingleObject(file1, -1);	
	DWORD CurSize = 0;
	//读取是否成功
	if (GetOverlappedResult(file1, &overlapped, &CurSize, TRUE))//False:不会等待,True:一直等待
	{
		printf("文件内容: %s\n", buff);
		printf("实际读写数量: %d\n", CurSize);
	}
	CloseHandle(file1);
	return 0;
}

该技术只能用于一个设备只发送一个IO请求,否则,若一个设备对应多个操作,当句柄受信时无法判断是该设备的那个操作完成。


事件内核对象

针对每个I/O操作绑定一个内核事件对象,并将等待事件等待函数等待该事件的受信,当I/O操作完成后系统使得与该操作绑定的事件受信,从而判断那个操作完成。

#include <Windows.h>
#include <iostream>

int main()
{
	HANDLE File1 = CreateFileW(L"test.txt", GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
	OVERLAPPED overlapped1{ 0 };
	OVERLAPPED overlapped2{ 0 };

	overlapped1.hEvent = CreateEventW(NULL, FALSE, FALSE, L"FileEvent1");
	overlapped2.hEvent = CreateEventW(NULL, FALSE, FALSE, L"FileEvent2");

	WCHAR buff1[100]{ 0 };
	WCHAR buff2[100]{ 0 };

	//注意: hEvent的事件作为读取文件时,我们无法获取读取的真正的字符数量,此处参数无意义NULL
	ReadFile(File1, buff1, 100, NULL, &overlapped1);
	ReadFile(File1, buff2, 100, NULL, &overlapped2);

	/*
	...
	*/

	//判断信号状态
	WaitForSingleObject(overlapped1.hEvent/*判断事件*/, -1);
	WaitForSingleObject(overlapped2.hEvent/*判断事件*/, -1);
	printf("buff1:%s\n", buff1);
	printf("buff2:%s\n", buff2);

	return 0;
}

可警醒IO

当发出设备IO请求时,同时要求我们传递一个被称为完成例程的回调函数,当IO请求完成时调用该回调函数完成我们需要处理的工作。

#include <Windows.h>
#include <iostream>

VOID WINAPI lpCompletionRoutine(
	DWORD dwErrorCode,
	DWORD dwNumberOfBytesTransfered,
	LPOVERLAPPED lpOverlapped
	)
{
	if (lpOverlapped->hEvent == (HANDLE)0x100)
	{
		printf("一\n");
	}
	else
	{
		printf("二\n");
	}
}


int main1()
{
	HANDLE File1 = CreateFileW(L"test.txt", GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
	OVERLAPPED overlapped1{ 0 };
	OVERLAPPED overlapped2{ 0 };
	overlapped1.hEvent = (HANDLE)0x100;
	overlapped2.hEvent = (HANDLE)0x101;
	WCHAR buff1[100], buff2[100];
	ReadFileEx(File1, buff1, 100, &overlapped1, lpCompletionRoutine);
	ReadFileEx(File1, buff2, 100, &overlapped2, lpCompletionRoutine);
	SleepEx(0, TRUE);

	return 0;
}

完成端口IO

我不会。。。。。。。 以后在来填坑

posted @ 2022-10-24 19:55  hugeYlh  阅读(114)  评论(0编辑  收藏  举报  来源