【问题标题】:Winapi Timer callback thread, never returnsWinapi Timer 回调线程,永不返回
【发布时间】:2014-06-18 09:05:02
【问题描述】:

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

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

=> 初始化()

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
* 优先级:正常

编辑2: 有更多时间来处理这种奇怪的行为,我现在可以在独立代码中重现它。

#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() 之后,似乎某些线程在线程池中仍处于待处理状态,从而阻止了我的进程关闭。我还是想不通...

【问题讨论】:

  • 粘贴代码本身以进行推理会很有帮助。
  • DeleteTimerQueueEx 返回什么,如果为零,GetLastError 建议什么?
  • 它返回 1。我认为这意味着成功,对吧?
  • 我看不到您的所有代码,但看起来问题出在CreateTimerQueueTimer 的第一个参数上。您正在传递 timerHandle,但您应该将 reference 传递给 timerHandle。 IE。 &amp;timerHandle。这将解释继续调用计时器回调,因为您对 DeleteTimerQueueTimer 的调用传递了一个虚假的计时器句柄值。
  • 正如其他人所说,请显示代码。展示一个完整的、简短的程序来说明问题。如果你这样做,你会得到一个明确的答案。如果你不这样做,你会得到猜测。

标签: c++ c winapi


【解决方案1】:

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

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

这也可能是从回调中调用DeleteTimerQueueExDeleteTimerQueueTimer 的问题,这是被禁止的。中断 DeleteTimerQueueEx 的执行并查看您所在的线程,如果它是 TppWorkerThread,那么您发现了您的错误。

编辑:

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

我们将事件/消息发送到非计时器线程的队列,然后删除计时器,您甚至可以为此设置一个专用线程,但这可能是矫枉过正。归根结底,您需要确保在进行清理之前删除计时器,因此您必须阻止删除或让其他线程在发出事件信号时执行此操作。

【讨论】:

  • 实际上,我们确实在计时器回调中调用 DeleteTimerQueueTimer() .... 这就是我们不使用 INVALID_HANDLE_VALUE 选项的原因,因为我们不想从线程中创建死锁等着自己退出……那你有什么建议呢?我们应该在哪里调用 DeleteTimerQueueTimer() 来正确清除这个计时器?
  • 对于我阅读的内容,文档建议从回调中调用 DeleteTimerQueueTimer() 是允许的,只要您在 CompletionEvent 参数设置为 NULL 的情况下调用它。它应该返回 ERROR_IO_PENDING 因为回调仍在运行,然后计时器应该在退出时清理......但我不知道我的问题是计时器没有很好地清理还是回调线程在退出时卡住了某种僵局。
【解决方案2】:

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

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

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

  exit(0);

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-24
    • 2011-04-25
    • 2019-11-13
    • 1970-01-01
    • 2017-12-06
    • 2017-07-26
    • 1970-01-01
    相关资源
    最近更新 更多