根据Microsoft's Documentation,
当用户模式APC排队时,线程不会被指示调用APC函数,除非它处于可报警状态。线程处于可报警状态后,线程将按照先进先出(FIFO)顺序处理所有未决APC,等待操作返回WAIT_IO_COMPLETION。
据我所知,对SleepEx(0,TRUE)
的单个调用应该触发线程处理所有挂起的APC。所以我做了一个小程序来测试我的理解能力,但输出并不像我预期的那样。
程序创建一个WaitableTimer
对象,该对象将用户定义函数Triggered()
的多个函数调用排队。然后主线程转到Sleep()
几秒钟,允许WaitableTimer
对象排队对线程的多个调用。
当从Sleep()
唤醒时,主线程随后调用SleepEx(0,TRUE)
来清除线程队列中累积的APC。(我希望它的速率比Timer队列New APCS快。)预期的结果是Triggered()
将被多次调用,但实际上它只被调用一次。
有人能帮我找出我的概念或代码中的任何错误吗?谢谢你!
代码:
#include <stdio.h>
#include <Windows.h>
#include <cassert>
void Triggered([[maybe_unused]] LPVOID lpArgToCompletionRoutine,[[maybe_unused]] DWORD dwTimerLowValue,[[maybe_unused]] DWORD dwTimerHighValue)
{
printf("TRIGGERED!\n");
}
int wmain()
{
HANDLE hTimer{ ::CreateWaitableTimerW(nullptr, TRUE, L"MyTimer") };
assert(hTimer);
SYSTEMTIME st{};
FILETIME ft{};
st.wYear = 2020;
st.wDay = 15;
st.wHour = 15;
st.wMonth = 10;
st.wMinute = 15;
::TzSpecificLocalTimeToSystemTime(nullptr, &st, &st);
::SystemTimeToFileTime(&st, &ft);
LARGE_INTEGER dueTime{};
dueTime.QuadPart = *(LONGLONG*)&ft;//<<<<< I've also tried setting a relative lpDueTime value
assert(::SetWaitableTimer(hTimer, &dueTime, 100, Triggered, nullptr, FALSE));
::Sleep(1000);
::SleepEx(0,TRUE);
/* I also tried the following to replace ::SleepEx(0,TRUE)
** DWORD ret{WAIT_IO_COMPLETION};
** while (ret == WAIT_IO_COMPLETION)
** {
** ret=::SleepEx(0, TRUE);
** }
*/
assert(::CancelWaitableTimer(hTimer));
return 0;
}
2条答案
按热度按时间e5nqia271#
如果您阅读了SetWaitableTimer文档:
如果在线程进入可报警等待状态之前设置了计时器,则APC将被取消。
这意味着APC只会在线程实际处于可等待状态时排队,即当
SleepEx()
或类似函数正在运行时。因此,在Sleep(1000)
期间,计时器确实触发了,但APC从未排队。如果你将代码更改为始终只使用
SleepEx()
,那么它将“工作”:(顶部的
#include <chrono>
,并删除Sleep(1000)
和SleepEx(0, TRUE)
调用。对于相同的逻辑,你可以使用WinApi而不是std::chrono
,但我喜欢std::chrono
,因为它很简单。但是如果你做了
Sleep(1000)
以外的任何事情,你希望你的计时器在这个过程中执行,例如。执行一些计算或类似的,那么你想要做的将根本不工作。老实说,我建议不要使用APC-
CreateWaitableTimerW()
创建的计时器可以集成到标准事件循环(基于WaitForMultipleObjectsEx
/MsgWaitForMultipleObjectsEx
)中,这可能是最简单的方法。另一方面,如果你想确保没有定时器事件被丢弃,那么在后台启动另一个线程,它主要只是睡眠,但定期醒来,然后直接执行你需要的代码,或者每次发生定时器事件时在主线程中排队回调。(您希望使用哪种排队机制取决于主线程的组织方式。)
kuuvgm7e2#
系统分配与定时器相关单个
KAPC
对象。KAPC
只能插入一次螺纹。如果APC已经插入到线程-它不能被第二次插入。直到不会被移除。在Sleep(1000);
期间定时器发信号数次,但仅在第一个信号APC将被插入。当您调用SleepEx(0,TRUE);
时,此apc已交付