Поток обратного вызова Winapi Timer, никогда не возвращается

0

Мне нужно отлаживать код, который не от меня. Этот код реализует API-интерфейс таймера с использованием интерфейса таймера WinAPI. Я не очень привык к этой функциональности 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 debugger, я вижу те потоки (называемые TppWorkerThread @4) в потоке...

Возможно, мы пропустили что-то, чтобы вернуть обратный вызов, или мы создали какие-то тупики... Однако я не могу понять это...

Пожалуйста, дайте мне знать, если я забуду какую-то соответствующую информацию.

Спасибо за помощь.

РЕДАКТИРОВАТЬ:
Дальнейшая информация:
- Блокирующая нить находится в этом состоянии в конце процесса:
* Категория: Рабочие темы
* Имя: _TppWorkerThread @4
* Местоположение: _ZwWaitForWorkViaWorkerFactory @8
* Priotity: 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() некоторые потоки остаются в очереди на пул потоков, что предотвращает мой процесс. Я все еще не могу понять это...

  • 1
    было бы полезно вставить сам код, чтобы рассуждать об этом.
  • 0
    что возвращает DeleteTimerQueueEx и если ноль, что предлагает GetLastError?
Показать ещё 3 комментария
Теги:
winapi

2 ответа

0
Лучший ответ

После некоторой работы над этим вопросом, я думаю, я получил ответ. Похоже, что этот API-интерфейс timerQueue кодируется поверх threadPool winAPI, и когда мы просим создать timerQueue Windows, создайте пул потоков, из которого будет запущен весь обратный вызов. До сих пор нет проблем, но, когда мы запрашиваем удаление timerQueue, кажется, что этот пул потоков не удаляется... Этот результат в некотором потоке сохраняется в ожидании ожидающего использования и предотвращения возврата процесса.

Через некоторое время (тайм-аут??) эти потоки возвращаются и процесс завершается.

Я действительно не понимаю, почему этот пул не закрыт... но теперь я использую обходной путь:

  exit(0);

В конце моей программы это немного жестоко, но он выполняет эту работу (то есть: убивает мой процесс, какие нити остаются в ожидании или нет)

1

Когда вы вызываете DeleteTimerQueueEx с INVALID_HANDLE_VALUE качестве второго параметра, он будет блокироваться до тех пор, пока все обратные вызовы не будут выполнены. Ошибка может быть в одной из ваших функций обратного вызова, которая никогда не возвращается.

Вы вызываете DeleteTimerQueueTimer(timerQueue, timerHandle, NULL); с NULL в качестве третьего параметра, это не будет дожидаться завершения обратного вызова, если он запускается во время удаления таймера. Я предлагаю использовать DeleteTimerQueueTimer(timerQueue, timerHandle, INVALID_HANDLE_VALUE), который будет блокироваться до завершения вызова (если он выполняется). Вызов cleanUp() без использования блокирующей версии DeleteTimerQueueTimer, вероятно, является ошибкой, поскольку вы можете очищать одновременно с выполнением обратного вызова.

Также может возникнуть проблема вызова DeleteTimerQueueEx или DeleteTimerQueueTimer из обратного вызова, что запрещено. Перерыв в выполнении DeleteTimerQueueEx и посмотрите, в каком потоке вы находитесь, если его TppWorkerThread, чем вы нашли ошибку.

РЕДАКТИРОВАТЬ:

В своем комментарии вы говорите, что вы вызываете DeleteTimerQueueTimer из обратного вызова, но не используете INVALID_HANDLE_VALUE, снова прочитав документацию с http://msdn.microsoft.com/en-us/library/windows/desktop/ms682569%28v=vs.85% 29.aspx это кажется законным, но я отчетливо помню, как мы принимаем проектные решения, чтобы этого избежать, извините, что это настолько расплывчато, я надеюсь, что кто-то может дать авторитетный совет по этому поводу.

Мы отправляем событие/сообщение в очередь потока без таймера, который затем удаляет таймер, вы можете даже иметь выделенный поток для этого, но это, вероятно, слишком велико. В конце дня вам нужно быть уверенным, что таймер удаляется перед очисткой, поэтому вам нужно либо блокировать удаление, либо использовать какой-либо другой поток при сигнализации о событии.

  • 0
    На самом деле, мы вызываем DeleteTimerQueueTimer () из нашего обратного вызова таймера ... вот почему мы не использовали опцию INVALID_HANDLE_VALUE, так как мы не хотим создавать тупик из потока, ожидающего самого себя для выхода ... что ваше предложение тогда? где мы должны вызвать DeleteTimerQueueTimer (), чтобы правильно очистить этот таймер?
  • 0
    Из того, что я прочитал, документ предполагает, что вызов DeleteTimerQueueTimer () из обратного вызова является своего рода разрешенным, если вы вызываете его с параметром CompletionEvent, установленным в NULL. Он должен вернуть ERROR_IO_PENDING, так как обратный вызов все еще работает, а затем таймер должен быть чистым при выходе ... Но я не знаю, заключается ли моя проблема в том, что таймер не очищен должным образом или поток обратного вызова застревает при выходе в каком-то виде тупик.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню