【问题标题】:C++ Builder 10.2: Thread blocks WaitForInputIdleC++ Builder 10.2:线程块 WaitForInputIdle
【发布时间】:2017-11-22 08:21:36
【问题描述】:

我有以下场景:应用程序 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 将按预期返回。这是一个解决方案,虽然我知道文章 WaitForInputIdle waits for any thread, which might not be the thread you care about,但我想了解这里实际发生的情况。

这是应用程序A的简化源代码。它只是由一个TForm和从TThread派生的类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的空闲状态?

【问题讨论】:

    标签: c++ vcl


    【解决方案1】:

    启动的应用永远没有机会进入“输入空闲”状态。

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

    即使工作线程使用SendMessage() 而不是PostMessage(),它也会跨线程边界发送消息,因此在接收线程(在这种情况下,主 UI 线程)之前,不会将每条消息分派到窗口致电(Peek|Get)Message()。这在SendMessage() documentation 中有说明:

    线程之间发送的消息只有在接收线程执行消息检索代码时才被处理。发送线程被阻塞,直到接收线程处理完消息。

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

    WaitForInputIdle() documentation 说:

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

    主 UI 线程有来自线程的连续消息未决,因此它不能成为“输入空闲”。并且工作线程不检索自己的入站消息,因此它也不能成为“输入空闲”(参见WaitForInputIdle waits for any thread, which might not be the thread you care about)。

    因此,整个应用程序进程永远不会变成“输入空闲”,因此WaitForInputIdle() 会阻塞,直到超时,在本例中为INFINITE,因此它会无限期阻塞。

    Form 的 OnShow 事件直到主 UI 消息循环运行并且已经处理了几个其他窗口消息之后才会触发,因此应用程序有时间进入“输入空闲”状态,解除阻塞 WaitForInputIdle(),在线程被创建并开始向窗口发送消息。

    【讨论】:

    • 感谢您的回答!还有一件事我不明白:您写道,线程发送后续消息,它们之间有 1 毫秒的延迟,但在我看来,它只发送一条消息,并且在处理完之后就空闲了。调用 Sleep() 的时间间隔没有任何区别。所以我所期望的是主 UI 消息循环将处理它从线程获得的单个消息,因此解除阻塞后者,然后通过调用 Sleep() 函数进入空闲状态。所以应该没有理由让消息队列变得拥挤。
    • 线程在尝试检索消息但没有可用消息时变为输入空闲状态。它在进入睡眠状态时不会变为输入空闲状态。在你的例子中。 sleep(1) 的时间延迟太小,工作线程会在上一条消息发送完毕后立即发送下一条消息。所以主 UI 线程没有机会尝试检索消息并看到没有消息可用。如果增加睡眠间隔,主 UI 线程将有更多时间等待新消息,因此它可能会在此期间变为输入空闲
    • 感谢您的澄清。因此,据我了解,主 UI 线程很忙,而工作线程被一次调用 SendMessage() 阻塞。之后主 UI 线程有机会在工作线程忙时进入空闲输入(由于调用 Sleep())。这就解释了为什么增加给 Sleep() 的时间段并不能改善这种情况。我希望我做对了。我发现的唯一解决方法是在调用 SendMessage() 之前插入一个对 Sleep() 的调用。
    • 正如你已经说过的,工作线程不会检索自己的入站消息,因此它不能成为“输入空闲”。所以我刚刚验证了在工作线程内调用 PeekMessage() 可以解决问题,它会导致工作线程的“空闲输入”状态并解除对外部 WaitForIdleInput 的阻塞。最后一个问题:WaitForInputIdle() 是否仅在工作线程第一次出现在主 UI 的消息循环中后才对其进行调查?
    • @CheviN:这一定是 VCL 问题,而不是 Windows 问题。当工作线程发送消息时,VCL 中的某些内容可能会被触发,并且某些内容会阻止主线程正确地“输入空闲”。当我完全不使用 VCL 进行相同的测试时,只使用直接的 Win32 API,测试工作得非常好,一旦消息循环在SendMessage() 之后没有任何要处理的内容,启动的应用程序的主线程就会“输入空闲”。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多