【Windows原理】异步IO-_APC(异步过程调用)


// 同步IO的缺点是, 在读写文件时, 如果文件太大, 或者读写的时间太长, 就会在读写函数中
// 阻塞住.  
// 异步IO解决了这个问题, 异步IO读写文件时, 文件再大也不会阻塞住
// 但是异步IO要完成这样的特性是有一点付出的
// 异步读写文件后, 需要通过一些方式才能知道文件读写(IO任务)什么时候完成. 
// 这里讲的是第三个方式, 通过设置IO完成函数来处理已完成的IO任务.
// 在Windows 中, ReadFile和WriteFile还有另外一个版本的函数, 那就是ReadFileEx 和
// WriteFileEx, 这两个增强版的函数中, 都是添加了一个参数, 这个参数是一个函数指针.
// 当I/O任务完成之后, 系统内部会在一段时间内调用这个函数指针. 但是系统并不是无条件调
// 用该函数指针的. 
// 每个线程都有一个APC队列(异步过程调用队列), 这个队列实际上就是一个数组, 这个数组中
// 的每一个元素都是一个函数的地址. 线程被挂起时, 这些函数会被系统调用, 系统会从APC队
// 列中移除已经被调用过的函数.
// 线程挂起时, 默认是不执行APC队列中的函数的, 想要在线程挂起时让系统自动调用APC队列中
// 的函数, 必须使用以下的函数来挂起线程:
//   1. SleepEx
//   2. WaitForSignalObjectEx
//   3. WaitForMultipleObjectEx
//   4. SignalObjectAndWait
//   5. GetQueuedCompletionStatusEx
//   6. MsgWaitMultipelObjectEx
// 这些函数中,有一个参数 BOOL bAlertble 这个参数就用于指定是否调用APC队列中的函数
// 的.异步I/O中的可提醒I/O通知方式实际的原理是这样的:当I/O任务完成后,系统会将完成函
// 数的地址发送到线程APC队列( 通过QueueUserAPC 发送 ),然后当线程被指定的函数挂起 
// 后 ,可提醒I/O的完成函数才会被调用.

#include "stdafx.h"
#include <windows.h>
#include <process.h>
struct MyOVERLAPPED:public OVERLAPPED
{
public:
	char * pBuff; // 用于保存缓冲区首地址
	int	nIndex;    // 用于保存IO任务的序号
	MyOVERLAPPED();
	MyOVERLAPPED(int nSize, int nFileOffsetLow, int nFileOffsetHeight = 0);
	~MyOVERLAPPED();
};

// 完成函数
VOID WINAPI OverlappedCompliteFun(
	DWORD dwErrorCode,				// 错误码
	DWORD dwNumberOfBytesTransfered, // 成功读写的字节数
	LPOVERLAPPED lpOverlapped		 // 重叠IO结构体指针
	)
{
	MyOVERLAPPED* pMyOverlapped = (MyOVERLAPPED*)lpOverlapped;
	printf("[%d]IO任务完成,读取字节:%d,读取位置:%d,读取内容:[%s]\n",pMyOverlapped->nIndex,pMyOverlapped->Internal,pMyOverlapped->Offset,pMyOverlapped->pBuff);

	// 释放空间
	delete pMyOverlapped;
}

int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE hFile = CreateFile(L"1.txt",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,NULL);

	if (hFile == INVALID_HANDLE_VALUE)return 0;

	int nFileRead = 50;
	int nReadPos = 0;

	// 第一个IO任务
	MyOVERLAPPED * vo = new MyOVERLAPPED(nFileRead,nReadPos);
	vo->nIndex = 0;
	ReadFileEx(hFile, vo->pBuff, nFileRead, vo, OverlappedCompliteFun);

	// 第二个IO任务
	nFileRead = 80;
	nReadPos = 500;
	MyOVERLAPPED * vo2 = new MyOVERLAPPED(nFileRead,nReadPos);
	vo2->nIndex = 1;  // 任务序号
	ReadFileEx(hFile, vo2->pBuff, nFileRead, vo2, OverlappedCompliteFun);

	// 如果将下面这行代码注释掉,完成函数将不会被调用
	SleepEx(3, TRUE);
	system("pause");

	return 0;
}

// 默认构造函数
MyOVERLAPPED::MyOVERLAPPED() :pBuff(nullptr)
{
	OVERLAPPED::hEvent = 0;
}

// 初始化构造函数
MyOVERLAPPED::MyOVERLAPPED(int nIoSIze, int nFileOffsetLow, int nFileOffsetHeight)
{
	// 创建事件对象
	OVERLAPPED::hEvent = 0; // 不需要事件对象

	// 保存文件读写偏移
	OVERLAPPED::Offset = nFileOffsetLow;
	OVERLAPPED::OffsetHigh = nFileOffsetHeight;

	// 申请缓冲区保存文件内容
	pBuff = new char[nIoSIze]{};
}

// 析构函数,释放空间,事件对象
MyOVERLAPPED::~MyOVERLAPPED()
{
	// 释放空间
	if (pBuff != nullptr)
	{
		delete[] pBuff;
		pBuff = nullptr;
	}
}

 

文 / luna
LEAVE A REPLY

loading