C++ Builder 10.2:线程块 WaitForInputIdle

C++ Builder 10.2: Thread blocks WaitForInputIdle

我遇到了以下情况:应用程序 B 应该启动应用程序 A,然后等待 A 变为空闲状态。为此,B将CreateProcess与WaitForInputIdle结合使用,不幸的是这个命令的超时时间已经设置为INFINITE。

这是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 将按预期 return。这是一个解决方案,虽然我知道这篇文章 WaitForInputIdle waits for any thread, which might not be the thread you care about,但我想了解这里实际发生的事情。

这是应用程序A的简化源代码。它只包含一个TForm和从TThread派生的class TestThread。

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的空闲状态?

启动的应用永远不会有机会进入 "input idle" 状态。

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

即使工作线程正在使用 SendMessage() 而不是 PostMessage(),它正在跨线程边界发送消息,因此直到接收线程才会将每条消息分派到 window (在本例中,主 UI 线程)调用 (Peek|Get)Message()。这在 SendMessage() documentation:

中说明

Messages sent between threads are processed only when the receiving thread executes message retrieval code. The sending thread is blocked until the receiving thread processes the message.

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

WaitForInputIdle() documentation 说:

The WaitForInputIdle function enables a thread to suspend its execution until the specified process has finished its initialization and is waiting for user input with no input pending. If the process has multiple threads, the WaitForInputIdle function returns as soon as any thread becomes idle.

主UI线程有连续的消息从线程挂起,所以它不能变成"input idle"。并且工作线程不检索自己的入站消息,因此它也不能成为 "input idle"(参见 WaitForInputIdle waits for any thread, which might not be the thread you care about)。

因此,整个应用程序进程永远不会变成 "input idle",因此 WaitForInputIdle() 会阻塞,直到其超时结束,在本例中为 INFINITE,因此它会无限期地阻塞。

表单的 OnShow 事件直到主 UI 消息循环 运行 并且已经处理了其他几条 window 消息之后才触发,因此应用程序在创建线程并开始向 window.

发送消息之前,有时间 "input idle"、解除阻塞 WaitForInputIdle()