You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何调用线程队列中所有APC?代码测试异常求排查

APC触发问题排查

微软官方文档说明:当用户模式APC被排入队列后,线程只有处于可警报状态时才会调用APC函数。线程进入可警报状态后,会按先进先出(FIFO)顺序处理所有待处理的APC,等待操作返回WAIT_IO_COMPLETION。

我理解单次调用SleepEx(0,TRUE)应该能触发线程处理所有待处理的APC,于是写了个程序验证,但结果不符合预期。程序创建WaitableTimer对象,试图多次将自定义的Triggered()函数排入APC队列。主线程先调用Sleep()休眠几秒,让定时器累积多个APC,之后调用SleepEx(0,TRUE)清理队列(预期处理速度比定时器新增APC快)。预期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;//<<<<我也试过设置相对的lpDueTime值

    assert(::SetWaitableTimer(hTimer, &dueTime, 100, Triggered, nullptr, FALSE));
    ::Sleep(1000);

    ::SleepEx(0,TRUE);

    /* 我也试过用下面的代码替换::SleepEx(0,TRUE)
    ** DWORD ret{WAIT_IO_COMPLETION};
    ** while (ret == WAIT_IO_COMPLETION)
    ** {
    **   ret=::SleepEx(0, TRUE);
    ** }
    */
     
    assert(::CancelWaitableTimer(hTimer));  
    return 0;
}

问题分析与解决

核心错误点

  1. WaitableTimer的APC队列逻辑误解:当使用SetWaitableTimer设置周期性定时器时,每次定时器到期只会向队列排入一个APC,且存在关键限制:如果前一次的APC还未被处理,系统不会为后续的定时器到期事件重复排入APC。也就是说,队列中最多只会存在一个该定时器的APC,不会累积多个。这就是你只看到一次TRIGGERED!输出的直接原因。

  2. SleepEx(0, TRUE)的行为:SleepEx(0, TRUE)确实会让线程进入可警报状态并立即检查APC队列,处理所有待处理的APC后返回,但由于定时器的APC根本没有累积,所以自然只会执行一次。

修正方案

方案1:手动排入APC验证逻辑

如果要验证“处理所有待处理APC”的逻辑,不能依赖WaitableTimer的周期性APC,改用QueueUserAPC手动向线程队列排入多个APC即可:

#include <stdio.h>
#include <Windows.h>
#include <cassert>

void Triggered([[maybe_unused]] ULONG_PTR dwParam)
{
    printf("TRIGGERED!\n");
}

int wmain()
{
    // 手动排入3个APC
    for(int i=0; i<3; ++i)
    {
        QueueUserAPC((PAPCFUNC)Triggered, GetCurrentThread(), 0);
    }

    // 处理所有APC
    DWORD ret = WAIT_IO_COMPLETION;
    while(ret == WAIT_IO_COMPLETION)
    {
        ret = SleepEx(0, TRUE);
    }

    return 0;
}

这段代码会输出3次TRIGGERED!,符合“单次进入可警报状态处理所有待处理APC”的预期。

方案2:用WaitableTimer实现多次触发

如果一定要用WaitableTimer实现多次APC触发,需要确保每次APC处理完成后,定时器的下一次到期才会排入新的APC。让线程持续处于可警报状态,及时处理每个APC即可:

#include <stdio.h>
#include <Windows.h>
#include <cassert>

HANDLE g_hTimer;

void Triggered([[maybe_unused]] LPVOID lpArgToCompletionRoutine,[[maybe_unused]] DWORD dwTimerLowValue,[[maybe_unused]] DWORD dwTimerHighValue)
{
    static int count = 0;
    printf("TRIGGERED! %d\n", ++count);
    // 触发10次后停止定时器
    if(count >= 10)
    {
        CancelWaitableTimer(g_hTimer);
    }
}

int wmain()
{
    g_hTimer = ::CreateWaitableTimerW(nullptr, TRUE, L"MyTimer");
    assert(g_hTimer);

    // 设置相对超时,立即触发,周期100ms
    LARGE_INTEGER dueTime{};
    dueTime.QuadPart = -100000; // 10ms(单位为100纳秒)
    assert(::SetWaitableTimer(g_hTimer, &dueTime, 100, Triggered, nullptr, FALSE));

    // 持续处于可警报状态,处理APC直到定时器被取消
    while(true)
    {
        DWORD ret = SleepEx(INFINITE, TRUE);
        if(ret != WAIT_IO_COMPLETION)
        {
            break;
        }
    }

    CloseHandle(g_hTimer);
    return 0;
}

这段代码会输出10次TRIGGERED!,因为每次APC被处理后,定时器的下一次到期会继续排入新的APC,线程持续在可警报状态处理它们。

内容的提问来源于stack exchange,提问作者bugger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 04:42:40