c++ 如何调用线程队列中的所有APC?

i7uaboj4  于 2023-06-25  发布在  其他
关注(0)|答案(2)|浏览(197)

根据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()将被多次调用,但实际上它只被调用一次。
有人能帮我找出我的概念或代码中的任何错误吗?谢谢你!
代码:

  1. #include <stdio.h>
  2. #include <Windows.h>
  3. #include <cassert>
  4. void Triggered([[maybe_unused]] LPVOID lpArgToCompletionRoutine,[[maybe_unused]] DWORD dwTimerLowValue,[[maybe_unused]] DWORD dwTimerHighValue)
  5. {
  6. printf("TRIGGERED!\n");
  7. }
  8. int wmain()
  9. {
  10. HANDLE hTimer{ ::CreateWaitableTimerW(nullptr, TRUE, L"MyTimer") };
  11. assert(hTimer);
  12. SYSTEMTIME st{};
  13. FILETIME ft{};
  14. st.wYear = 2020;
  15. st.wDay = 15;
  16. st.wHour = 15;
  17. st.wMonth = 10;
  18. st.wMinute = 15;
  19. ::TzSpecificLocalTimeToSystemTime(nullptr, &st, &st);
  20. ::SystemTimeToFileTime(&st, &ft);
  21. LARGE_INTEGER dueTime{};
  22. dueTime.QuadPart = *(LONGLONG*)&ft;//<<<<< I've also tried setting a relative lpDueTime value
  23. assert(::SetWaitableTimer(hTimer, &dueTime, 100, Triggered, nullptr, FALSE));
  24. ::Sleep(1000);
  25. ::SleepEx(0,TRUE);
  26. /* I also tried the following to replace ::SleepEx(0,TRUE)
  27. ** DWORD ret{WAIT_IO_COMPLETION};
  28. ** while (ret == WAIT_IO_COMPLETION)
  29. ** {
  30. ** ret=::SleepEx(0, TRUE);
  31. ** }
  32. */
  33. assert(::CancelWaitableTimer(hTimer));
  34. return 0;
  35. }
e5nqia27

e5nqia271#

如果您阅读了SetWaitableTimer文档:
如果在线程进入可报警等待状态之前设置了计时器,则APC将被取消。
这意味着APC只会在线程实际处于可等待状态时排队,即当SleepEx()或类似函数正在运行时。因此,在Sleep(1000)期间,计时器确实触发了,但APC从未排队。
如果你将代码更改为始终只使用SleepEx(),那么它将“工作”:

  1. auto now = std::chrono::steady_clock::now();
  2. auto timeout = now + std::chrono::seconds{ 1 };
  3. while (now < timeout) {
  4. DWORD timeoutMs = static_cast<DWORD>(std::chrono::duration_cast<std::chrono::milliseconds>(timeout - now).count());
  5. ::SleepEx(timeoutMs, TRUE);
  6. now = std::chrono::steady_clock::now();
  7. }

(顶部的#include <chrono>,并删除Sleep(1000)SleepEx(0, TRUE)调用。对于相同的逻辑,你可以使用WinApi而不是std::chrono,但我喜欢std::chrono,因为它很简单。
但是如果你做了Sleep(1000)以外的任何事情,你希望你的计时器在这个过程中执行,例如。执行一些计算或类似的,那么你想要做的将根本不工作。
老实说,我建议不要使用APC-CreateWaitableTimerW()创建的计时器可以集成到标准事件循环(基于WaitForMultipleObjectsEx/MsgWaitForMultipleObjectsEx)中,这可能是最简单的方法。
另一方面,如果你想确保没有定时器事件被丢弃,那么在后台启动另一个线程,它主要只是睡眠,但定期醒来,然后直接执行你需要的代码,或者每次发生定时器事件时在主线程中排队回调。(您希望使用哪种排队机制取决于主线程的组织方式。)

展开查看全部
kuuvgm7e

kuuvgm7e2#

系统分配与定时器相关单个KAPC对象。KAPC只能插入一次螺纹。如果APC已经插入到线程-它不能被第二次插入。直到不会被移除。在Sleep(1000);期间定时器发信号数次,但仅在第一个信号APC将被插入。当您调用SleepEx(0,TRUE);时,此apc已交付

相关问题