转载自【win32多线程程序设计】 译者--侯捷
增加了部分笔记内容
使用 overlapped I/O 并搭配 event 对象,会产生两个基础性问题。
第一个问题是,使用 WaitForMultipleObjects(),你只能够等待最多达 MAXIMUM_ WAIT_OBJECTS 个对象。在 Windows NT 3.x 和 4.0 所提供的 Win32 SDK 中,此最大值为 64(#define MAXIMUM_WAIT_OBJECTS 64 // Maximum number of wait objects)。如果你要等待 64 个以上的对象,就会出问题。所以即使在一个客户/服务器环境(client-server)中,你也只能同时拥有 64 个连接点。
第二个问题是,你必须不断根据“哪一个 handle 被激发”而计算如何反应。你必须有一个分派表格(dispatch table )和 WaitForMultipleObjects() 的 handles 数组结合起来。
这两个问题可以靠一个所谓的异步过程调用(Asynchronous Procedure Call,APC)解决。只要使用“Ex” 版的 ReadFile() 和 WriteFile() ,你就可以调用这个机制。这两个函数允许你指定一个额外的参数,是一个 callback 函下才行。如果有一个 I/O 操作完成,而线程不处于 “alertable” 状态,那么对 I/O completion routine 的调用就会暂时被保留下来。因此,当一个线程终于进入 “alertable” 状态时,可能已经有一大堆储备的 APCs 等待被处理。
如果线程因为以下五个函数而处于等待状态,而其 “alertable” 标记被设为 TRUE ,则该线程就是处于 “alertable” 状态:
SleepEx()
WaitForSingleObjectEx()
WaitForMultipleObjectsEx()
MsgWaitForMultipleObjectsEx()
SignalObjectAndWait()
“只有当程序处于 “alertable” 状态下时,APCs 才会被调用”这个观念是很重要的。其结果就是,当你的程序正在进行精确至小数点后10000位的圆周率的计算时,I/O completion routine 不会被调用。如果原线程正忙于重绘屏幕,也不会有另一个线程被使用。
你所提供的 I/O completion routine 应该有这样的型式(样式,参数表相同,函数名可以不同。):
VOID WINAPI FileIOCompletionRoutine(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransferred,
LPOVERLAPPED lpOverlapped
};
参数 :
dwErrorCode 这个参数内含以下的值:0 表示操作完成,
ERROR_HANDLE_EOF 表示操作已经到了文件尾端。
dwNumberOfBytesTransferred 真正被传输的数据字节数。
lpOverlapped 指向 OVERLAPPED 结构。此结构由开启 overlapped I/O 操作的函数提供。
I/O completion routine 需要一些东西以了解其环境。如果它不知道 I/O 操作完成了什么,它也就很难决定对此数据要做些什么。使用 APCs 时,OVERLAPPED 结构中的 hEvent 栏位不需要用来放置一个 event handle。
Win32 文件上说此时 hEvent 栏位可以由程序员自由运用。那么最大的用途就是:首先配置一个结构,描述数据来自哪里,或是要对数据进行一些什么操作,然后将 hEvent 栏位设定指向该结构。
#include <iostream>
#include "windows.h"
#include <WinBase.h>
#include <stdio.h>
using namespace std;
#define MAX_REQUEST 5
#define MAX_SIZE 24
#define MAX_TRY_COUNT 5
char szBuff[MAX_REQUEST][MAX_SIZE + 1];
LONG lCompletioncount;
HANDLE g_hEventOverLapped;
HANDLE g_hFile;
OVERLAPPED g_stOverLapped[MAX_REQUEST];
void PrintError(const char* strLineDesc, const char* pszFileName, int nLine, DWORD dwErrNum)
{
LPSTR lpError;
char szErrorLog[512] = {0};
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErrNum, LANG_NEUTRAL, (LPTSTR)&lpError, 0, NULL);
sprintf_s(szErrorLog, "The Fllowing call failed at line %d in %s : \n %s\nReason:%s\n", nLine, pszFileName, strLineDesc, lpError);
#ifdef _WINDOWS_
DWORD dwNumRead;
WriteFile(GetStdHandle(STD_ERROR_HANDLE), szErrorLog, strlen(szErrorLog), &dwNumRead, FALSE);
Sleep(3000);
#else
char szModuleName[MAX_PATH] = {0};
GetModuleFileName(NULL, szModuleName, MAX_PATH);
MessageBox(NULL, szErrorLog, szModuleName, MB_ICONWARNING | MB_OK | MB_TASKMODAL | MB_SETFOREGROUND);
#endif
}
#define MTVERIFY(a) if (!(a)) PrintError(#a, __FILE__, __LINE__, GetLastError());
void WINAPI FileIOCompletionRoutine(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped)
{
int nIndex = (int)(lpOverlapped->hEvent);
cout << "Read#" << nIndex << "returned" << dwErrorCode << " and " << dwNumberOfBytesTransfered << "word were read" << endl;
//InterlockedIncrement(&lCompletioncount);
lCompletioncount++;
if (lCompletioncount >= MAX_REQUEST)
{
SetEvent(g_hEventOverLapped);
}
Sleep(1000);
}
void CheckOSVesion()
{
OSVERSIONINFO stVersion = {0};
stVersion.dwOSVersionInfoSize= sizeof(stVersion);
BOOL bRes = GetVersionEx(&stVersion);
if ((TRUE != bRes )||
(stVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
{
cout << "IOByAPC must be running under Windows NT" << endl;
exit(EXIT_FAILURE);
}
}
DWORD WINAPI QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount)
{
g_stOverLapped[nIndex].hEvent= (HANDLE)nIndex;
g_stOverLapped[nIndex].Offset= dwLocation;
// 读文件
BOOL bRes = FALSE;
DWORD dwError;
for (int i = 0; i < MAX_TRY_COUNT; i++)
{
bRes = ReadFileEx(g_hFile, szBuff[nIndex], dwAmount, &g_stOverLapped[nIndex], FileIOCompletionRoutine);
if (TRUE == bRes)
{
cout << "Read Queue#" << nIndex << "Overlapped" << endl;
return TRUE;
}
else
{
dwError = GetLastError();
if (dwError == ERROR_INVALID_USER_BUFFER ||
dwError == ERROR_NOT_ENOUGH_QUOTA ||
dwError == ERROR_NOT_ENOUGH_MEMORY)
{
Sleep(500);
continue;
}
// 严重错误
break;
}
}
cout << "ReadFileEx Failed!" << endl;
return -1;
}
void main()
{
CheckOSVesion();
// 创建文件句柄
g_hFile = CreateFile("E:\\Msg\\Log\\sys 2014-08-21.log",
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
MTVERIFY(INVALID_HANDLE_VALUE != g_hFile);
// 创建约束事件
MTVERIFY(g_hEventOverLapped = CreateEvent(NULL, TRUE, FALSE, NULL));
// 开启异步读数据功能
for (int i = 0; i < MAX_REQUEST; i++)
{
QueueRequest(i, i * 1024, MAX_SIZE);
}
// 等待读取完毕
DWORD dwRes;
for (; ; )
{
dwRes = WaitForSingleObjectEx(g_hEventOverLapped, INFINITE, TRUE);// bAlertable 设置为TRUE时,回调函数// FileIOCompletionRoutine() 才会被调用
if (WAIT_OBJECT_0 == dwRes)
{
cout << "Wait Over!" << endl;
break;
}
if(WAIT_IO_COMPLETION == dwRes)
{
cout << "Complete Cont=" << lCompletioncount << endl;
}
}
// 测试数据
cout << "Read#1 datas: " << endl;
cout << szBuff[0] << endl;
cout << "Read#2 datas: " << endl;
cout << szBuff[1] << endl;
cout << "Read#3 datas: " << endl;
cout << szBuff[2] << endl;
cout << "Read#4 datas: " << endl;
cout << szBuff[3] << endl;
cout << "Read#5 datas: " << endl;
cout << szBuff[4] << endl;
MTVERIFY(CloseHandle(g_hFile));
}
本例的 QueueRequest() 函数非常类似 IOBYEVNT 中的同名函数。最大的差别在于并没有把 “returned im mediately” 和 “not com plete” 区分开来。你可以看到,这个函数也处理了“系统缺乏资源”的情况。 本例新增的函数是 FileIOCom pletionRoutine()。这是一个 callback 函数,当 overlapped I/O 完成的时候,由系统调用之。 最后,请你注意,main() 调用 WaitForSingleO bjectEx(),所以 APCs 会被处理(串行处理,通过Sleep函数调用可知)。我们必须在一个循环中完成此事,因为在处理完 APCs 之后,WaitForSingleObjectEx() 会传回 WAIT_IO_COMPLETION(最后回传WAIT_OBJECT_0,区间有何区别,暂时不得而知,知识缺口)。