Windows 内核对象(五) 可等待计时器内核对象
可等待计时器
有时我们可能需要在某些时间点上或者按照某些频率启动线程或者检测一些东西
这就需要用到我们的可等待计时器
可以使用CreateWaitableTimer API创建一个可等待计时器内核对象
- 它也有信号状态
- 它也分为
手动重置和自动重置两种
WaitableTimer被创建后, 是无信号状态的
CreateWaitableTimer
HANDLE WINAPI CreateWaitableTimer(
_In_opt_ LPSECURITY_ATTRIBUTES lpTimerAttributes,
_In_ BOOL bManualReset,
_In_opt_ LPCTSTR lpTimerName
);lpTimerAttributes
- 安全属性
- 一般都是传递NULL
bManualReset
- 手动或者自动重置信号
- TRUE 手动重置
- FALSE 自动重置
lpTimerName
- 对象的名称
- 在跨进程使用的时候, 需要使用
名称打开
CreateWaitableTimer创建完成后, 它是无信号状态的
通过SetWaitableTimer来设置可等待计时器对象的信号状态
SetWaitableTimer
BOOL WINAPI SetWaitableTimer(
_In_ HANDLE hTimer,
_In_ const LARGE_INTEGER *pDueTime,
_In_ LONG lPeriod,
_In_opt_ PTIMERAPCROUTINE pfnCompletionRoutine,
_In_opt_ LPVOID lpArgToCompletionRoutine,
_In_ BOOL fResume
);hTimer
- 可等待计时器内核对象的句柄
- 可以是通过
CreateWaitableTimer创建的一个 - 可以是通过
OpenWaitableTimer使用名称打开的一个
pDueTime
触发信号的时间点
- 以
100纳秒为单位设置信号 - 是一个
UTC时间, 使用 FILETIME 结构体描述 - 传递负数表示一个
相对时间 实际的定时精度取决于硬件的能力
- 以
lPeriod
可等待定时器的周期
- 以
毫秒为单位 - 也是一个
UTC时间 - 传递
0则只触发一次信号, 通常配合手动模式的定时器 传递
> 0的值则周期性自动重新激活信号- 会一直触发, 直到调用
CancelWaitableTimer取消定时器 - 或者重新调用
SetWaitableTimer进行复位
- 会一直触发, 直到调用
- 传递
< 0则失败
- 以
- 手动重置状态下, 此参数如果 > 0, 会不停的触发, 并不会按照设置的周期触发, 没有意义.
pfnCompletionRoutine
- 设置APC回调函数
- 详情见 APC回调
lpArgToCompletionRoutine
- 传送给回调函数的参数
fResume
- TRUE, 将系统从节能模式中恢复
- FALSE, 不恢复
- 一般都是FALSE
使用例子
#include <Windows.h>
#include <process.h>
#include <tchar.h>
INT _tmain()
{
// 创建一个手动匿名的可等待计时器对象
HANDLE hTime = CreateWaitableTimer(NULL, TRUE, NULL);
if (WaitForSingleObject(hTime, 20) == WAIT_TIMEOUT)
_tprintf(TEXT("Wait Timeout....\n"));
// 通过代码我们可以得知, WaitableTimer创建完成后是无信号状态
// 使用相对时间
LARGE_INTEGER liDueTime;
// 1s = 1000ms = 1000000微秒 = 1000000000纳秒 = 1000000000000皮秒 = 10 ^ 15飞秒 = 10 ^ 18啊秒 = 10 ^ 21仄秒 = 10 ^ 24幺秒
// 这里设置 10秒, 也就是 10 000 000 000 纳秒
// 注意, 这个参数的单位是 100ns, 所以设置 100 000 000
// 使用相对时间, 最终是 -100 000 000
liDueTime.QuadPart = -100000000LL;
// 设置10秒后有信号
SetWaitableTimer(hTime, &liDueTime, 1000, NULL, NULL, FALSE);
if (WaitForSingleObject(hTime, INFINITE) == WAIT_OBJECT_0)
_tprintf(TEXT("WaitableTimer is Signaled.....\n"));
CloseHandle(hTime);
return 0;
}自动和手动状态
实验代码:
#include <Windows.h>
#include <process.h>
#include <tchar.h>
HANDLE g_hTimer = INVALID_HANDLE_VALUE;
CRITICAL_SECTION g_Cs;
UINT WINAPI ThreadFunc(LPVOID lParam)
{
while (WaitForSingleObject(g_hTimer, INFINITE) == WAIT_OBJECT_0)
{
EnterCriticalSection(&g_Cs);
_tprintf(TEXT("Thread [%d] is Runing.....\n"), (INT)lParam);
// 手动模式下 1秒的定时触发周期不管用
// 必须再次调用SetWaitableTimer
//LARGE_INTEGER liDueTime;
//liDueTime.QuadPart = -10000000LL;
//SetWaitableTimer(g_hTimer, &liDueTime, 1000, NULL, NULL, FALSE);
LeaveCriticalSection(&g_Cs);
}
return 0;
}
INT _tmain()
{
InitializeCriticalSection(&g_Cs);
// 创建一个手动匿名的可等待计时器对象
//g_hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
// 创建一个自动匿名的可等待计时器对象
g_hTimer = CreateWaitableTimer(NULL, FALSE, NULL);
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (LPVOID)1, 0, NULL);
// 注释下一行代码查看1条线程的效果
HANDLE hThread2 = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (LPVOID)2, 0, NULL);
// 使用相对时间
LARGE_INTEGER liDueTime;
// 1s = 1000ms = 1000000微秒 = 1000000000纳秒 = 1000000000000皮秒 = 10 ^ 15飞秒 = 10 ^ 18啊秒 = 10 ^ 21仄秒 = 10 ^ 24幺秒
// 这里设置 1秒, 也就是 1 000 000 000 纳秒
// 注意, 这个参数的单位是 100ns, 所以设置 10 000 000
// 使用相对时间, 最终是 -10 000 000
liDueTime.QuadPart = -10000000LL;
// 设置1秒后有信号 间隔1秒触发
SetWaitableTimer(g_hTimer, &liDueTime, 1000, NULL, NULL, FALSE);
WaitForSingleObject(hThread, INFINITE);
DeleteCriticalSection(&g_Cs);
CloseHandle(hThread);
// 注释下一行代码查看1条线程的效果
CloseHandle(hThread2);
CloseHandle(g_hTimer);
return 0;
}在多条线程执行的时候, 我们发现线程1和线程2都可以进行执行
这和我们之前所学的, 线程抢占式执行有些冲突啊?
其实不是
在Wait状态下, 线程是不可调度的, `不存在抢占式执行`
我们的操作系统有一套算法
这套算法会`尽可能的保证每个Wait能够得到信号`
它会`尽可能的保持公平`
包括我们的`事件内核对象`也是一样的 注: 我实验的结果并不是这样的
在自动模式下, 线程一条一条的执行
在手动模式下, 线程同时可以执行
似乎和电脑配置有关系, 请见仁见智, 自主实验
手动重置状态下
- 它的信号状态不会被
自动改变 - 它的定时周期一半都设置为0, 表示只触发一次
- 它的信号状态不会被
自动重置状态下
- 可以通过调用
CancelWaitableTimer取消定时器
- 可以通过调用
APC回调
函数原型:
VOID CALLBACK TimerAPCProc(
_In_opt_ LPVOID lpArgToCompletionRoutine,
_In_ DWORD dwTimerLowValue,
_In_ DWORD dwTimerHighValue
);此参数将会被压入
调用SetWaitableTimer的线程的APC队列- APC是一个异步调用过程
- 线程空闲下来后, 回去执行APC队列中的东西
必须使线程进入监听状态, 可以使用下面的函数
- SleepEx
- WaitForSingleObjectEx
- WaitForMultipleObjectsEx
- MsgWaitForMultipleObjectsEx
- SignalObjectAndWait
此时, 无论是手动模式还是自动模式, 都会周期性触发APC回调
- 通常APC回调会配合
手动模式来进行使用
- 通常APC回调会配合
例子:
#include <Windows.h>
#include <process.h>
#include <tchar.h>
HANDLE g_hTime = INVALID_HANDLE_VALUE;
UINT WINAPI ThreadAPCTest(LPVOID lParam)
{
while (WaitForSingleObject(g_hTime, INFINITE) == WAIT_OBJECT_0)
_tprintf(TEXT("ThreadAPCTest is runing....\n"));
return 0;
}
VOID CALLBACK TimerAPCProc(
_In_opt_ LPVOID lpArgToCompletionRoutine,
_In_ DWORD dwTimerLowValue,
_In_ DWORD dwTimerHighValue
)
{
while (WaitForSingleObject(g_hTime, INFINITE) == WAIT_OBJECT_0)
_tprintf(TEXT("APC Callback is runing....\n"));
}
INT _tmain()
{
g_hTime = CreateWaitableTimer(NULL, TRUE, NULL);
//g_hTime = CreateWaitableTimer(NULL, FALSE, NULL);
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadAPCTest, NULL, 0, NULL);
LARGE_INTEGER li = { 0 };
// 将一个函数压入到调用SetWaitableTimer的线程的APC队列中
// 设置立即有信号
SetWaitableTimer(g_hTime, &li, 2000, TimerAPCProc, NULL, FALSE);
// 触发APC
SleepEx(INFINITE, TRUE);
CloseHandle(hThread);
CloseHandle(g_hTime);
return 0;
}未完待续...
如有错误,请提出指正!谢谢.
本文由 花心胡萝卜 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: 2017-07-13 at 02:56 pm