C++ Builder 10.2: Thread blocks WaitForInputIdle

C++ Builder 10.2: Thread blocks WaitForInputIdle

本文关键字:Thread blocks WaitForInputIdle Builder C++      更新时间:2023-10-16

>我有以下情况:应用程序 B 应该启动应用程序 A,然后等待 A 变为空闲状态。为此,B 将 CreateProcess 与 WaitForInputIdle 结合使用,不幸的是,此命令的超时已设置为无限。

这是 B 源代码的相应部分:

void StartA(void){
STARTUPINFO         siInfo;
PROCESS_INFORMATION piInfo;
ZeroMemory(&siInfo, sizeof(siInfo));
ZeroMemory(&piInfo, sizeof(piInfo));
CreateProcess(L"A.exe", L"", 0, 0, 
false, CREATE_DEFAULT_ERROR_MODE, 0, 0,
&siInfo, &piInfo);
WaitForInputIdle(piInfo.hProcess, INFINITE); // A will block this command!
CloseHandle(piInfo.hProcess);
CloseHandle(piInfo.hThread);
}

当我从这个应用程序 B 调用我的应用程序 A 时,由于 A 的 TestThread 调用了 SendMessage 命令,B 将被其 WaitForInputIdle 命令永远阻止。如果我只在 FormShow 事件之后创建 TestThread,那么 WaitForInputIdle 将按预期返回。这是一个解决方案,虽然我知道文章 WaitForInputIdle 等待任何线程,这可能不是您关心的线程,但我喜欢了解这里实际发生的事情。

这是应用程序 A 的简化源代码。它只是由一个TForm和TestThread类组成,TestThread是从TThread派生出来的。

class TestThread : public TThread {
public:
__fastcall TestThread(HWND in_msg) : msg(in_msg), TThread(false) {};
virtual __fastcall ~TestThread(){};
private:
void __fastcall Execute(){
// Next line leads to WaitForInputIdle blocking
SendMessage(msg, WM_USER, NULL, NULL); 
while(!Terminated) Sleep(1);
}
HWND msg;
};
class TFormA : public TForm{
private:
TestThread * testthread_p;
public:
__fastcall TFormA(TComponent* Owner){
testthread_p = new TestThread(Handle);
}
virtual __fastcall ~TFormA(){}
}; 

为什么命令 WaitForInputIdle 无法检测到应用程序 A 的空闲状态?

启动的应用程序永远不会有机会进入"输入空闲"状态。

假设TFormA是应用程序的MainForm,它在应用程序启动时创建,从而在VCL的主UI消息循环开始运行之前创建线程(当WinMain()调用Application->Run()时)。

即使工作线程使用的是SendMessage()而不是PostMessage(),它也在跨线程边界发送消息,因此在接收线程(在本例中为主 UI 线程)调用(Peek|Get)Message()之前,每条消息都不会调度到窗口。 这在SendMessage()文档中有说明:

仅当接收线程执行消息检索代码时,才会处理线程之间发送的消息。发送线程将被阻止,直到接收线程处理消息。

当主 UI 消息循环开始运行时,来自线程的一条消息已经发送到窗口,正在等待调度。 发送后续消息时,它们之间的延迟为 1 毫秒。 因此,当主 UI 消息循环第一次尝试从队列中检索消息时,已经有一条消息在等待。当循环调度该消息进行处理并返回到队列以等待消息时,已经有一条新消息在等待它。

WaitForInputIdle()文档说:

WaitForInputIdle函数使线程能够暂停其执行,直到指定的进程完成其初始化并等待用户输入,没有输入挂起。如果进程有多个线程,则只要任何线程空闲WaitForInputIdle函数就会返回。

主 UI 线程具有来自线程的连续消息挂起,因此它不能变为"输入空闲"。工作线程不会检索自己的入站消息,因此它也不能成为"输入空闲"(请参阅 WaitForInputIdle 等待任何线程,这可能不是你关心的线程)。

因此,应用程序进程作为一个整体永远不会变得"输入空闲",因此WaitForInputIdle()阻塞直到其超时过去,在这种情况下是INFINITE,因此它无限期地阻塞。

窗体的OnShow事件直到主 UI 消息循环运行很久之后才会触发,并且已经处理了其他几个窗口消息,因此应用有时间进入"输入空闲",取消阻止WaitForInputIdle(),然后创建线程并开始向窗口发送消息。