平时在编写程序时,我们经常会遇到设备I/O操作问题。一般的来说,对于具有小量数据或者速度快的设备进行I/O操作时,我们可以使用同步I/O的方法来读写数据。但是,如果要读取大容量的设备或者访问设备时间较长时,实行异步I/O操作可以确保我们的程序不会因为同步I/O等待而无法响应其他的工作。
那么,异步I/O操作是如何告知应用程序I/O操作应经完成的呢?一般来说,有4中方法:
- 触发设备内核对象
- 触发事件内核对象
- 可提醒I/O
- I/O完成端口
下面,我主要将以下第三种方法:可提醒I/O。
我们知道,系统创建一个线程的时候,会创建一个与线程相关联的队列,我们称之为APC(异步调用过程)队列。这个队列到底是用来干嘛的呢?要知道,Microsoft从来不会做无聊的事(假设^_^)。当我们发出一个I/O请求(ReadFileEx)时,如果在该请求函数参数中指定了完成函数(也可以说是回调函数)的地址,那么首先该请求将这个完成函数的地址告知设备驱动程序,当设备驱动程序处理完I/O请求时,又将该地址以及要传给回调函数的值归为一项放到线程的APC队列中(APC里面放的什么东东,现在我们终于知道啦)。然后,如果线程是可提醒状态,那么他会从APC队列中取出一项来处理(也就是,调用完成函数),处理完后,如果APC不为空,继续处理,直至为空,之后可提醒函数的调用返回。通常,我们用6个可提醒函数将线程设为可提醒态。
从上面的描述中,我们不难得知,为了实现可提醒I/O,我们必须建一个回调函数,但是由于这些函数没有与某个问题有关的信息,所以我们无法针对某类问题做更多的处理,为此,我们可能得为不同的请求建立不同的回调函数,这无疑增加了程序的复杂性;其次,我们的每一个线程发出请求的处理也是由该线程来实施的,如果一个线程发出多个请求,那么APC的调用却是一个接着一个,效率上也不是太高。这两点影响了可提醒I/O的发展。
除了由设备驱动程序添加APC项外,Windows也允许我们手动地添加一项到APC队列中。
说的都是理论的东西,大家不容易理解,所以,请看下面的一个实例:
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
//Asynchronizing procedure call function
VOID CALLBACK APCFun(ULONG_PTR dwParam)
{
//Do nothing
printf("APC fuction is called.....\n");
}
UINT WINAPI ThreadFunc (PVOID pvParam)
{
HANDLE hEvent = (HANDLE)pvParam;
//Wait in an alertable state so that we can be forced to exit cleanly
printf("Wait for the event object....\n");
DWORD dw = WaitForSingleObjectEx(hEvent, INFINITE, TRUE);
printf("last error is : %d\n",GetLastError());
printf("Thread quits wait state.....\n");
//The event is signaled
if (dw == WAIT_OBJECT_0)
{
printf("The event object is signaled....\n");
}
//APC is called
if (dw == WAIT_IO_COMPLETION )
{
printf("Thread quits the wait state because a APC is put in the thread's queue....\n");
return 0;
}
return 0;
}
void main()
{
//Create a event kernel object , auto-reset, nonsignaled
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (PVOID)hEvent, 0, NULL);
Sleep(300);
QueueUserAPC(APCFun, hThread, NULL);
//Wait for the thread to exit
printf("Wait for the thread to exit....\n");
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hEvent);
CloseHandle(hThread);
getchar();
}
结果如下: