【问题标题】:Launch an application and wait until it has finished without blocking redraw启动应用程序并等待它完成而不阻止重绘
【发布时间】:2017-10-17 01:19:18
【问题描述】:

我有一个交互式 Win32 应用程序,有时我需要启动另一个应用程序并等待另一个应用程序完成。在其他应用程序运行期间,交互式应用程序不应响应调整窗口大小和移动窗口(这当然意味着交互式应用程序仍应继续重绘)。

我目前的做法是这样的:

  1. 创建线程T
  2. 禁用主窗口(使用EnableWindow(handle, FALSE)
  3. 继续消息循环
  4. 从线程 T 发送的特殊 WM_APP 消息将再次启用主窗口 (EnableWindow(handle, TRUE));

线程 T:

  1. 使用CreateProcess启动应用程序
  2. 等到应用程序终止使用WaitForSingleObject
  3. 使用PostMessage 在主窗口中发布特殊的WM_APP 消息。
  4. 线程在这里终止

这很好用,但我不确定这是否是正确的方法。

有更好的方法还是我应该继续这样?

【问题讨论】:

  • 听起来很合理。你有什么具体的疑问吗?
  • @ThingyWotsit 不是真的,我只是想听听其他意见。
  • 嗯,这就是我会直接做的,没有任何进一步的考虑。它肯定会起作用,那么为什么还要麻烦更复杂的解决方案呢? :)
  • 最好的 - 使用MsgWaitForMultipleObjectsEx - 你绝对不需要创建额外的线程
  • 我的意思是,你可以搞乱消息循环的输入,比如 MsgWaitForMultipleObjectsEx()。这意味着修改已经可以正常工作的代码,所以我不会为了避免另一个线程而烦恼。如果它已经很简单、相当高效并且运行良好,为什么还要修补:)

标签: c multithreading winapi


【解决方案1】:

你的方法很好,只要确保使用PostMessage()

向主窗口发送一条特殊的 WM_APP 消息

如果主线程恰好在等待线程T,以避免死锁。

正如评论者所说,创建线程的替代方法是使用带有MsgWaitForMultipleObjectsEx 的消息循环。

我看到的优势:

  • 不需要额外的线程,所以不会担心线程相关的问题,如竞争条件和死锁。这些问题通常很难发现和调试(通常它们只发生在客户机器上),所以我尽量避免使用线程。
  • 创建进程和等待进程的程序流程更简单。它可以是顺序的,而不是像线程解决方案那样基于事件的。

您必须自己判断这是否适合您的场景更好

可用于等待进程(或任何其他可等待句柄)同时处理消息的函数如下所示。它的实现非常复杂(有关背景信息,请参阅我的答案末尾的链接),但使用起来非常简单(参见后面的示例)。

// Function to process messages until the state of any of the given objects is signaled, 
// the timeout is reached or a WM_QUIT message is received.
// Parameter hDialog can be nullptr, if there is no dialog.
//
// Returns ERROR_SUCCESS if any of the given handles is signaled.
// Returns ERROR_TIMEOUT in case of timeout.
// Returns ERROR_CANCELLED if the WM_QUIT message has been received.
// Returns the value of GetLastError() if MsgWaitForMultipleObjectsEx() fails.

DWORD WaitForObjectsWithMsgLoop( 
    HWND hDialog, const std::vector<HANDLE>& handles, DWORD timeOutMillis = INFINITE )
{
    if( handles.empty() )
        return ERROR_INVALID_PARAMETER;

    DWORD handleCount = static_cast<DWORD>( handles.size() );
    DWORD startTime = GetTickCount();
    DWORD duration = 0;
    do
    {
        DWORD status = MsgWaitForMultipleObjectsEx(
            handleCount, handles.data(), 
            timeOutMillis - duration, 
            QS_ALLINPUT, 
            MWMO_INPUTAVAILABLE );

        if( status == WAIT_FAILED )
        {
            // MsgWaitForMultipleObjectsEx() has failed.
            return GetLastError();
        }
        else if( status >= WAIT_OBJECT_0 && status < WAIT_OBJECT_0 + handleCount )
        {
            // Any of the handles is signaled.
            return ERROR_SUCCESS;
        }
        else if( status == WAIT_OBJECT_0 + handleCount )
        {
            // New input is available, process it.
            MSG msg;
            while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
            {
                if( msg.message == WM_QUIT )
                {
                    // End the message loop because of quit message.
                    PostQuitMessage( static_cast<int>( msg.wParam ) );
                    return ERROR_CANCELLED;
                }
                // Enable message filter hooks (that's what the system does in it's message loops).
                // You may use a custom code >= MSGF_USER.
                // https://blogs.msdn.microsoft.com/oldnewthing/20050428-00/?p=35753
                if( ! CallMsgFilter( &msg, MSGF_USER ) )
                {
                    // Optionally process dialog messages.
                    if( ! hDialog || ! IsDialogMessage( hDialog, &msg ) )
                    {
                        // Standard message processing.
                        TranslateMessage( &msg );
                        DispatchMessage( &msg );
                    }
                }
            }
        }

        duration = GetTickCount() - startTime;
    }
    while( duration < timeOutMillis );

    // Timeout reached.
    return ERROR_TIMEOUT;
}

该函数可用于如下对话框过程。为简洁起见,省略了错误处理。

INT_PTR CALLBACK YourDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
    static bool s_childProcessRunning = false;

    switch( message )
    {
        case YourMessageToLaunchProcess:
        {
            // prevent reentrancy in case the process is already running
            if( s_childProcessRunning )
            {
                MessageBoxW( hDlg, L"Process already running", L"Error", MB_ICONERROR );
                return TRUE;
            }
            // Prepare CreateProcess() arguments
            STARTUPINFO si{ sizeof(si) };
            PROCESS_INFORMATION pi{};
            wchar_t command[] = L"notepad.exe"; // string must be writable!

            // Launch the process
            if( CreateProcessW( NULL, command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ) )
            {
                // Set flag to prevent reentrancy. 
                s_childProcessRunning = true;  

                // Wait until the child process exits while processing messages
                // to keep the window responsive.  
                DWORD waitRes = WaitForObjectsWithMsgLoop( hDlg, { pi.hProcess } );
                // TODO: Check waitRes for error

                s_childProcessRunning = false;

                // Cleanup
                CloseHandle( pi.hThread );
                CloseHandle( pi.hProcess );
            }
            return TRUE;
        }

        // more message handlers...
    }
    return FALSE;
}

强制性旧新事物链接:

【讨论】:

  • 非常感谢。很好的答案。我已经在使用PostMessage,问题中的措辞不是最佳的。
  • 好吧,'这样就不用担心线程相关的问题了,比如竞争条件和死锁'什么?怎么会有这种事?这只是顺序代码 - 创建进程,等待它完成,postMessage。 '创建进程并等待它的程序流程更简单。它可以是顺序的,而不是像线程解决方案那样基于事件的。 - 这是完全完全颠倒和不正确的。
  • '所以我尽量避免线程' - 我注意到 - 你正在传播关于“竞争条件和死锁”的不必要的 FUD,而在此应用程序中发生这种情况的可能性为零。跨度>
  • 您本质上是在事件处理程序循环中调用 DoEvents() 并使用标志来防止重入。那是嗯....'不是最佳的':(
  • @ThingyWotsit 我认为没有像微不足道的线程这样的东西。正确的模式对你我来说可能是显而易见的,但对于经验不足的开发人员来说肯定不是。即使在这种“简单”的情况下(PostMessage()SendMessage()),死锁问题也非常真实,并且代码会不断发展,不会一直保持简单。
猜你喜欢
  • 2010-10-11
  • 1970-01-01
  • 1970-01-01
  • 2021-05-19
  • 1970-01-01
  • 2011-12-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多