Estou enfrentando um problema raro em que SetThreadpoolTimerEx não invoca a função de retorno de chamada conforme esperado.
Ambiente:
Sistema operacional: Windows 10 22H2 (Build 19045.5131)
Visual Studio: 2022 (Versão 17.12.0)
SDK e conjunto de ferramentas: Windows SDK 10.0.19041.0, conjunto de ferramentas de plataforma v143
Emitir:
De acordo com a documentação do SetThreadpoolTimerEx ( link ):
Se o estado anterior do temporizador foi "definido" e a função retornar FALSE, então um retorno de chamada está em andamento ou prestes a começar.
No entanto, no meu caso, a função retorna FALSE, mas o callback não está sendo chamado. Isso faz com que meu programa trave enquanto espera o callback sinalizar a conclusão.
Código C++ para reproduzir:
#include <iostream>
#include <atomic>
#include <chrono>
#include <Windows.h>
std::atomic<bool> callbackDone = false;
VOID(NTAPI callback)(
_Inout_ PTP_CALLBACK_INSTANCE Instance,
_Inout_opt_ PVOID Context,
_Inout_ PTP_TIMER Timer
)
{
printf("callback...\n");
callbackDone = true;
}
int main() {
bool canFail = false;
while (true) {
callbackDone = false;
auto timer = CreateThreadpoolTimer(callback, nullptr, nullptr);
LARGE_INTEGER relativeTime = {};
relativeTime.QuadPart = 10000000 * -1;
FILETIME relativeTimeFt = {};
relativeTimeFt.dwLowDateTime = relativeTime.LowPart;
relativeTimeFt.dwHighDateTime = static_cast<DWORD>(relativeTime.HighPart);
auto setRes1 = SetThreadpoolTimerEx(timer, &relativeTimeFt, 0, 0);
Sleep(999);
auto setRes2 = SetThreadpoolTimerEx(timer, nullptr, 0, 0);
if (setRes2 == TRUE) {
FILETIME now = {};
SetThreadpoolTimerEx(timer, &now, 0, 0);
}
else {
if (!callbackDone) {
WaitForThreadpoolTimerCallbacks(timer, FALSE);
canFail = true;
}
}
if (canFail) {
printf("waiting for possibly failed timer\n");
}
auto startWaitTime = std::chrono::high_resolution_clock::now();
auto waitMsgStep = std::chrono::seconds(10);
auto nextMsgTime = waitMsgStep;
while (!callbackDone) {
SleepEx(10, TRUE);
auto waitDuration = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::high_resolution_clock::now() - startWaitTime
);
if (waitDuration > nextMsgTime) {
printf("waiting for callback to finish for %d seconds...\n", static_cast<DWORD>(nextMsgTime.count()));
nextMsgTime += waitMsgStep;
}
}
if (canFail) {
canFail = false;
printf("timer is ok\n");
}
CloseThreadpoolTimer(timer);
}
}
Saída possível:
Problema:
Apesar de SetThreadpoolTimerEx retornar FALSE — o que, de acordo com a documentação, indica que o retorno de chamada está em andamento ou prestes a começar — a função de retorno de chamada não está sendo chamada em alguns casos raros. Isso faz com que o programa fique travado indefinidamente no loop esperando que callbackDone se torne verdadeiro.
Questões:
Existe algum problema conhecido com SetThreadpoolTimerEx em que o retorno de chamada pode não ser invocado mesmo que a função retorne FALSE?
Estou usando incorretamente as APIs do temporizador do pool de threads de uma forma que pode causar esse comportamento?
Como posso modificar meu código para garantir que o retorno de chamada seja sempre invocado ou para tratar corretamente o caso quando isso não for feito?
Além disso, do blog de Raymond Chen :
A última linha é a interessante: quando você cancela o timer ou o objeto wait, o thread pool tenta recuperar quaisquer retornos de chamada pendentes, mas às vezes um retorno de chamada já foi longe demais e não pôde ser recuperado. Por exemplo, o retorno de chamada pode já estar em andamento. Nesse caso, a função Set...Ex retorna FALSE para informar que você ainda não terminou. Você tem que esperar o retorno de chamada ser concluído antes que tudo esteja finalmente pronto.
No meu caso, SetThreadpoolTimerEx retorna FALSE, e espero 10 segundos ou mais, mas o retorno de chamada nunca é invocado.