以前第一次看到APC,也就是异步过程调用的时候,没有搞明白这是一个什么样的机制。
今天再看一次,然后结合一些简单的测试代码和搜索来的资料,大概明白了是怎么回事。
先来非常简单地描述一下这一个“可等待的计时内核对象”。
大概定义如下:
可等待的计时器对象是这样一种内核对象,它们会在某个指定的时间触发,或每一段时间触发一次,它们通常用来在某个时间执行一些操作。
相关的API(具体参数请参阅MSDN):
创建一个可等待的计时器:CreateWaitableTimer
得到一个已经存在的可等待的计时器的句柄:OpenWaitableTimer
设置可等待的计时器:SetWaitableTimer
可等待的计时器添加APC调用
Miscrosoft允许计时器把一个异步过程调用(Asynchronnous procedure call,APC)放到调用线程的队列中。将这一个异步过程调用放到调用线程的队列中,是通过调用SetWaitableTimer并利用类型为PTIMERAPCROUTINE的一个参数,这个参数其实是一个函数指针类型,所以异步过程的地址就是传到这里。想要线程调用这些异步过程,线程还必须处于可提醒状态,也就是通过调用SleepEx,WaitForSingleObjectEx,WaitForMultipleObjectEx,MsgWaitForMultipleObjectEx而进入的等待状态。这些函数都有一个参数,指是线程是否进入可提醒状态。
示例代码:
#define _WIN32_WINNT 0x0400
#include<windows.h>
#include<windowsx.h> //这里面只包含一些宏,不包括一些类型定义
#include<tchar.h>
#include<stdio.h>
VOID APIENTRY TimerAPCRoutine(PVOID pvArgToCompletionRoutine,
DWORD dwTimerLowValue,DWORD dwTimerHighValue) ;
int main(void)
{
HANDLE hTimer ;
LARGE_INTEGER li ;
hTimer = CreateWaitableTimer(NULL,FALSE,NULL) ;
const int nTimerUnitsPerSecond = 10000000 ;
li.QuadPart = -(5 * nTimerUnitsPerSecond) ; //负值代表相对时间
SetWaitableTimer(hTimer,&li,6*60*60*1000,TimerAPCRoutine,NULL,FALSE) ;
printf("Main Thread Sleep for INFINE\n") ;
SleepEx(INFINITE,TRUE) ;
printf("Main Thread Wake Up\n") ;
CloseHandle(hTimer) ;
return 0 ;
}
VOID APIENTRY TimerAPCRoutine(PVOID pvArgToCompletionRoutine,
DWORD dwTimerLowValue,DWORD dwTimerHighValue)
{
printf("APC Fuction\n") ;
return ;
}
程序大概的执行流程就是:创建一个可等待的计时器并添加一个异步过程调用,随之调用SleepEx进入可提醒状态。当计时器触发时,调用SetWaitableTimer的线程就会调用APC队列中的一项,也就是我们刚才传入的函数地址。执行完毕后,线程随即从SleepEx调用中返回。大概流程就是这样。
PS:如果调用SleepEx时,APC队列不为空,则线程不会睡眠,而是调用APC队列中的一项,直到APC队列为空。当调用那些让线程睡眠的函数时,当且仅当线程的APC队列中一项都没有的时候,这些函数才会将线程挂起。
还值得一提的是,这一个让可等待的计时器添加APC调用的机制,有点类似Linux下的处理alarm信号的信号处理函数。