Winapi定时器回调线程,永远不会返回

Winapi Timer callback thread, never returns

本文关键字:返回 永远 定时器 回调 线程 Winapi      更新时间:2023-10-16

我必须调试一些不是我的代码。这段代码使用winapi定时器接口实现了一个定时器API。我不太习惯这个Winapi功能,所以我可以使用您的帮助:)

根据我的理解,这段代码是这样完成的:

Init ()

=>

timerQueue = CreateTimerQueue();

=> CreateTimer ()

CreateTimerQueueTimer(timerHandle, timerQueue, timerCallback, ..., WT_EXECUTEDEFAULT);

=> timerCallback ()

 DeleteTimerQueueTimer(timerQueue , timerHandle,  NULL));
 calback() //Launch user-defined callback

=> CleanUp()//在结束时调用

DeleteTimerQueueEx(timerQueue , INVALID_HANDLE_VALUE);

当我们测试时,用户定义的回调在期望的时间后成功执行。但在此之后,timerCallback线程一直挂起并且永远不会返回,从而阻止所有进程返回。使用VS调试器,我可以看到线程上的那些线程(命名为TppWorkerThread@4)…

也许我们错过了一些使回调正确返回的东西,或者我们创建了某种死锁……然而,我不明白……

如果我忘记了一些相关信息,请告诉我。

谢谢你的帮助。

编辑:
更多信息:
阻塞线程在进程结束时处于此状态:
*类别:工作线程
*名称:_TppWorkerThread@4
*位置:_ZwWaitForWorkViaWorkerFactory@8
*优先级:Normal

EDIT2:有了更多的时间来处理这个奇怪的行为,我现在可以在一个独立的代码中重现它了。

#include <windows.h>
#include <stdio.h>
HANDLE gDoneEvent;
HANDLE hTimer[5];
HANDLE hTimerQueue = NULL;
HANDLE g_threadHandle;
void PeriodicCallback(void)
{
  printf("Periodic routine called.n");
}
void SingleCallback(void)
{
  printf("Single routine called.n");
  if (!DeleteTimerQueueTimer(hTimerQueue, hTimer[2], NULL))
    printf("DeleteTimerQueueTimer() fail. Return value is %d.n", GetLastError());
}
void CALLBACK CommonCallback(PVOID lpParam, BOOLEAN TimerOrWaitFired)
{
  printf("Common routine called. Parameter is %d.n", *(int *)lpParam);
  ((void (*)(void))lpParam)();
}
void MainTest(void)
{
  // Use an event object to track the TimerRoutine execution
  gDoneEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  if (NULL == gDoneEvent)
  {
    printf("CreateEvent failed (%d)n", GetLastError());
    return -1;
  }
  if(0 == SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL))
  {
    printf("SetThreadPriority failed (%d)n", GetLastError());
    return -2;
  }
  // Create the timer queue.
  hTimerQueue = CreateTimerQueue();
  if (NULL == hTimerQueue)
  {
    printf("CreateTimerQueue failed (%d)n", GetLastError());
    return -3;
  }
/*
  if (!CreateTimerQueueTimer( &hTimer[2], hTimerQueue, 
    (WAITORTIMERCALLBACK)CommonCallback, &SingleCallback, 1000, 0, WT_EXECUTEDEFAULT))
  {
    printf("CreateTimerQueueTimer failed (%d)n", GetLastError());
    return -4;
  }
*/
  if (!CreateTimerQueueTimer( &hTimer[4], hTimerQueue, 
    (WAITORTIMERCALLBACK)CommonCallback, &PeriodicCallback, 10, 500, WT_EXECUTEDEFAULT))
  {
    printf("CreateTimerQueueTimer failed (%d)n", GetLastError());
    return -5;
  }
  // TODO: Do other useful work here 
  printf("Call timer routine in 10 seconds...n");
  Sleep(4000);
  CloseHandle(gDoneEvent);
  if (!DeleteTimerQueueTimer(hTimerQueue, hTimer[4],  INVALID_HANDLE_VALUE))
    printf("DeleteTimerQueueTimer failed (%d)n", GetLastError());
  // Delete all timers in the timer queue.
  if (!DeleteTimerQueueEx(hTimerQueue, INVALID_HANDLE_VALUE))
    printf("DeleteTimerQueue failed (%d)n", GetLastError());
  Sleep(1000);
  ExitThread(0);
}
int main(int argc, char **argv[])
{
  if(g_threadHandle == CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MainTest, NULL, 0, NULL))
    printf("Creation fail");
  ExitThread(0);
}

我正在VisualStudio 2010 Professional上编译这段代码。

似乎在调用DeleteTimerQueueTimer()之后,一些线程仍然在线程池上挂起,防止我的进程关闭。

当您使用INVALID_HANDLE_VALUE作为第二个参数调用DeleteTimerQueueEx时,它将阻塞,直到所有正在运行的回调都完成。错误可能在一个永远不会返回的回调函数中。

您正在调用DeleteTimerQueueTimer(timerQueue , timerHandle, NULL);与NULL作为第三个参数,这将不会等待回调完成,如果一个正在运行时,你删除计时器。我建议使用DeleteTimerQueueTimer(timerQueue , timerHandle, INVALID_HANDLE_VALUE),它将阻塞,直到回调完成(如果一个正在运行)。调用cleanUp()而不使用DeleteTimerQueueTimer的阻塞版本很可能是一个bug,因为你可能在回调执行的同时进行清理。

这也可能是在回调中调用DeleteTimerQueueExDeleteTimerQueueTimer的问题,这是禁止的。中断DeleteTimerQueueEx的执行,看看你在哪个线程,如果它是TppWorkerThread,那么你已经找到了你的错误。

编辑:

在您的评论中,您说您确实从回调中调用DeleteTimerQueueTimer,但不使用INVALID_HANDLE_VALUE,再次从http://msdn.microsoft.com/en-us/library/windows/desktop/ms682569%28v=vs.85%29.aspx阅读文档,这似乎是合法的,但我清楚地记得我们做出设计决策以避免这种情况,我很抱歉这是如此模糊,我希望有人能对此给出权威的建议。

我们发送一个事件/消息到一个非计时器线程的队列,然后删除计时器,你甚至可以有一个专门的线程,但这可能是多余的。在一天结束时,您需要确保在进行清理之前删除计时器,因此您必须在删除时阻塞,或者在事件发出信号时让其他线程执行此操作。

在对那个问题进行了一些研究之后,我想我得到了答案。看来这个timerQueue API是在threadPool winAPI之上编码的,当我们要求创建一个timerQueue窗口时,创建一个线程池,所有回调都将从那里启动。直到这里没有问题,但是,当我们要求删除timerQueue时,似乎这个线程池没有被删除…这将导致某些线程保持挂起等待使用,并阻止进程返回。

一段时间后(超时??),这些线程返回,进程退出。

我真的不明白为什么这个游泳池没有关闭…但是,现在,我使用了一个变通方法:

  exit(0);

在我的程序结束时,这有点残酷,但它完成了工作(即:杀死我的进程,无论线程是否仍然挂起)