內核對象--可等待計時器WaitableTimer(二)

上節,介紹了時間一到就觸發計時器對象;

這節,主要介紹時間一到觸發計時器對象,同時計時器向線程的APC隊列添加一個APC函數。當計時器被觸發的時候,如果線程處於可提醒狀態,系統會讓線程調用回調函數。


爲了添加一個APC到隊列中,需要按如下步驟操作:

1、實現一個APC函數

VOID    APIENTRY    TimerAPCToutine(

    PVOID  pvArgToCompletionRoutine,  //SetWaitableTimer制定的值,傳遞一些上下文信息

    DWORD  dwTimeLowValue, DWORD  dwTimeHighValue //兩個值表示計時器被觸發的時間

);

2、設置SetWaitableTimer的兩個參數值pfnCompletionRoutine和pvArgToCompletionRoutine

pfnCompletionRoutine: 設置APC函數地址,用以將該函數加入到APC隊列

pvArgToCompletionRoutine: 跟APC函數的一致

3、SetWaitableTimer調用者線程必須處於可提醒狀態,且該APC函數應該被同一線程(SetWaitableTimer調用者線程)調用

線程必須由如下函數進入等待狀態:SleepEx, WaitForSingleObjectEx, WaitForMultipleObjectsEx, MsgWaitForMultipleObjectsEx, SignalObjectAndWait

4、確保APC函數會在計時器的下一次被觸發之前執行結束

爲什麼要強調這一點呢?因爲,只有當所有的APC函數都處理完畢後,纔會返回到等待函數(WaiForSingleObjectEx等)。因此,必須確保自己的APC函數會在計時器再次觸發前執行完畢,否則APC函數的加入隊列的速度大於APC函數自身的執行速度。

// WaitableTimer.cpp : 定義控制檯應用程序的入口點。
//

#include "stdafx.h"
#include <Windows.h>
#include <atlstr.h>

HANDLE	g_hTimer = NULL;
HANDLE	g_hThread= NULL;
DWORD	g_dwThreadID_1 = 0;
DWORD	g_dwThreadID_2 = 0;

SYSTEMTIME	g_stime;
DWORD	ThreadHander(LPVOID lpThreadParameter);
VOID	TimerAPCRoutine(PVOID pvArgToCompletionRoutine, DWORD dwTimerLowValue, DWORD dwTimerHighValue);

int _tmain(int argc, _TCHAR* argv[])
{
#if 0
	//Create Thread
	g_hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&ThreadHander, &g_dwThreadID_1, 0, &g_dwThreadID_1);
	if(g_hThread != NULL) {
		printf("CreateThread() is succesful!\n");
		printf("Thread:%p, ThreadID:%d\n", g_hThread, g_dwThreadID_1);
	}
	//CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&ThreadHander, &g_dwThreadID_2, 0, &g_dwThreadID_2);

	//啓動線程
	//ResumeThread(g_hThread);
#endif
	//Create Waitable Timer
	g_hTimer = CreateWaitableTimer(NULL, FALSE, NULL);
	if(g_hTimer != NULL) {
		printf("CreateWaitableTimer() call is successful!\n");
		if (ERROR_ALREADY_EXISTS == GetLastError()) {
			printf("WaitableTimer have Already exists!\n");
		}
	}

	//設置定時器2014/1/25 21:02:00啓動時間, 啓動以後再間隔5秒啓動一次
	SYSTEMTIME	st;
	FILETIME	ftLocal, ftUTC;
	LARGE_INTEGER	liUTC = {0};
	st.wYear = 2014;
	st.wMonth= 1;
	st.wDayOfWeek = 0;
	st.wDay = 25;
	st.wHour= 21;
	st.wMinute = 16;
	st.wSecond = 0;
	st.wMilliseconds = 0;

	SystemTimeToFileTime(&st, &ftLocal);

	//轉換本地時間到UTC時間
	LocalFileTimeToFileTime(&ftLocal, &ftUTC);

	//轉換FILETIME到LARGE_INTEGER
	//liUTC.LowPart = ftUTC.dwLowDateTime;
	//liUTC.HighPart= ftUTC.dwHighDateTime;
	//liUTC.QuadPart= -5*10000000; //設置函數調用後5秒鐘開始執行

	GetLocalTime(&g_stime);
	printf("開始時間:%02d:%02d:%02d %03d\n", g_stime.wHour, g_stime.wMinute, g_stime.wSecond, g_stime.wMilliseconds);
	if (FALSE != SetWaitableTimer(g_hTimer,&liUTC, 10*1000, /*NULL*/(PTIMERAPCROUTINE)&TimerAPCRoutine, "Hello", TRUE) ) {
		printf("SetWaitableTimer() return TRUE!\n");
	}

	ThreadHander(&g_dwThreadID_1);

	int n=1;
	while(true) {
		Sleep(1000);
		//printf("main() %d\n", n++);
	}

	system("pause");
	return 0;
}

DWORD ThreadHander(LPVOID lpThreadParameter)
{
	int n=1;
	SYSTEMTIME	st;

	while(true) {
		switch(WaitForSingleObjectEx(g_hTimer, INFINITE, TRUE))
		{
		case	WAIT_OBJECT_0:
			GetLocalTime(&st);
			printf(">> 時間:線程%d, %02d:%02d:%02d %03d\n", *((int*)lpThreadParameter), st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);			
			break;
		case	WAIT_TIMEOUT:
			printf("WaitForSingleObject(): WAIT_TIMEOUT\n");
			break;
		case	WAIT_ABANDONED:
			printf("WaitForSingleObject(): WAIT_ABANDONED\n");
			break;
		case	WAIT_FAILED:
			printf("WaitForSingleObject(): WAIT_FAILED,錯誤%d\n", GetLastError());
			break;
		}
	}

	printf("線程退出:%d\n", *((int*)lpThreadParameter));
	return	0;
}

VOID	TimerAPCRoutine(PVOID pvArgToCompletionRoutine, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
{
	TCHAR	szBuf[512]={0};
	SYSTEMTIME	Local;
	FILETIME	ftUTC;
	ftUTC.dwLowDateTime = dwTimerLowValue;
	ftUTC.dwHighDateTime= dwTimerHighValue;

	//轉換UTC時間到本地時間
	FileTimeToSystemTime(&ftUTC, &Local);

	//格式化時間字符串
	GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &Local, NULL, szBuf, _countof(szBuf));
	_tcscat_s(szBuf, _countof(szBuf), TEXT(" "));
	GetTimeFormat(LOCALE_USER_DEFAULT, 0, &Local, NULL, _tcschr(szBuf, TEXT('\0')), (int)(_countof(szBuf) - _tcslen(szBuf)));
	printf("TimerAPCRoutine:%02d:%02d:%02d, pvArgToCompletionRoutine=%s\n", Local.wHour, Local.wMinute, Local.wSecond,(char*)pvArgToCompletionRoutine);
}


上面的例子有一個問題,就是線程不應該在等待一個計時器句柄的同時以可提醒的方式等待同一個計時器。仔細看上面的圖上箭頭所指的一行與下面幾行就會發現第一次是內核對象句柄,後面的纔是可提醒的;

正確的做法,如下:

比較執行結果,就會發現這種方法比上面的要好。

DWORD ThreadHander(LPVOID lpThreadParameter)
{
	int n=1;
	SYSTEMTIME	st;

	while(true) {
		SleepEx(INFINITE, TRUE);
		GetLocalTime(&st);
		printf(">> 時間:線程%d, %02d:%02d:%02d %03d\n", *((int*)lpThreadParameter), st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);			
#if 0
		switch(WaitForSingleObjectEx(g_hTimer, INFINITE, FALSE))
		{
		case	WAIT_OBJECT_0:
			GetLocalTime(&st);
			printf(">> 時間:線程%d, %02d:%02d:%02d %03d\n", *((int*)lpThreadParameter), st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);			
			break;
		case	WAIT_TIMEOUT:
			printf("WaitForSingleObject(): WAIT_TIMEOUT\n");
			break;
		case	WAIT_ABANDONED:
			printf("WaitForSingleObject(): WAIT_ABANDONED\n");
			break;
		case	WAIT_FAILED:
			printf("WaitForSingleObject(): WAIT_FAILED,錯誤%d\n", GetLastError());
			break;
		}
#endif
	}

	printf("線程退出:%d\n", *((int*)lpThreadParameter));
	return	0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章