您的位置:首页 > 其它

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

2016-03-17 14:53 423 查看
转载自【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,区间有何区别,暂时不得而知,知识缺口)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: