异步过程调用(Asynchronous Procedure Calls,APCs)

转载自【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  函
数地址。当一个 overlapped I/O   完成时,系统应该调用该 callback  函数。这个 callback  函数被称为 I/O com pletion routine ,因为系统是在某一个特别的 overlapped I/O 操作完成之后调用它。 
然而,Windows 不会贸然中断你的程序,然后调用你提供的这个 callback 函数。系统只有在线程说“好,现在是个安全时机”时才调用你的 callback  函
数。以 Window s  的说法就是:你的线程必须在所谓的 “alertable” 状态之

下才行。如果有一个 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,区间有何区别,暂时不得而知,知识缺口)。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值