代码之家  ›  专栏  ›  技术社区  ›  doron

在windows线程中等待句柄

  •  2
  • doron  · 技术社区  · 6 年前

    我有一个mfc应用程序,它使用 CreateProcess(...) . 我想在创建的进程终止时执行ui更新。通常,我会用 WaitForSingleObject WaitForMutlipleObject 关于返回过程 HANDLE 但这会阻塞gui线程(坏的)。

    我能想到的唯一解决方案是生成一个新线程,该线程可以等待句柄并在进程终止时发送消息。这并不理想。

    因此,是否可以向windows管理器注册句柄,并在进程终止时接收windows消息?

    5 回复  |  直到 6 年前
        1
  •  2
  •   zett42    6 年前

    你可以使用 RegisterWaitForSingleObject() 当进程结束时,通过回调获得通知。这个 RegisterWaitForSingleObject 函数在 thread pool 要等待进程,所以这应该是资源的最佳使用。正如雷蒙德•陈评论的那样:

    线程池可以将多个等待请求批处理为一个调用 waitformultipleobjects,所以摊余成本是线程的1/63。

    下面是win32 gui应用程序的一个最小示例。代码创建一个窗口,然后创建另一个自身实例作为子进程,由“/child”参数指示。它注册wait回调函数并运行常规消息循环。您可以调整窗口大小并移动窗口,以查看图形用户界面是否被阻止。当子进程结束时,系统异步调用wait回调,该回调将发布应用程序定义的消息。( WM_APP )对着窗户。当窗口收到消息时,它会立即调用 UnregisterWait() 取消等待。如引用所述,甚至使用 WT_EXECUTEONLYONCE 必须在等待完成时取消(但不是在回调中!)。然后窗口显示一个消息框,以证明它已收到消息。

    为了简洁起见,省略了错误处理。您应该检查每个api函数的返回值并调用 GetLastError() 万一 FALSE 返回。

    #pragma comment(linker, "/SubSystem:Windows")
    #include <windows.h>
    #include <string>
    
    int APIENTRY wWinMain( HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPWSTR lpCmdLine, int /*nCmdShow*/ )
    {
        if ( wcsstr( lpCmdLine, L"/child" ) )
        {
            MessageBoxW( nullptr, L"Hello from child process!", L"Child", MB_OK );
            return 0;
        }
    
        // Create window
    
        struct WindowData
        {
            HANDLE hWait = nullptr;
        }
        wndData;
    
        WNDCLASSW wc{};
        wc.hInstance = hInstance;
        wc.hCursor = LoadCursor( nullptr, IDC_ARROW );
        wc.hbrBackground = reinterpret_cast<HBRUSH>(GetStockObject( WHITE_BRUSH ));
        wc.lpszClassName = L"MyClass";
        wc.cbWndExtra = sizeof(LONG_PTR);
        wc.lpfnWndProc = []( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
        {
            switch ( message )
            {
            case WM_APP:
                {
                    // When the wait is completed, you must call the UnregisterWait or UnregisterWaitEx function to cancel 
                    // the wait operation. (Even wait operations that use WT_EXECUTEONLYONCE must be canceled.) 
                    WindowData* pWndData = reinterpret_cast<WindowData*>(GetWindowLongPtr( hWnd, 0 ));
                    UnregisterWait( pWndData->hWait );
                    pWndData->hWait = nullptr;
    
                    MessageBoxW( hWnd, L"Child process has ended!", L"Main", MB_OK );
                }
                break;
            case WM_DESTROY:
                PostQuitMessage( 0 );
                break;
            }
            return DefWindowProc( hWnd, message, wParam, lParam );
        };
        RegisterClassW( &wc );
    
        HWND hWnd = CreateWindowExW( 0, wc.lpszClassName, L"Main", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr );
    
        SetWindowLongPtr( hWnd, 0, reinterpret_cast<LONG_PTR>( &wndData) );
    
        // Create child process
        std::wstring cmd( MAX_PATH, L'\0' );
        cmd.resize( GetModuleFileNameW( nullptr, &cmd[0], cmd.size() ) );
        cmd = L"\"" + cmd + L"\" /child";
        STARTUPINFOW si{ sizeof( si ) };
        PROCESS_INFORMATION pi{};
        CreateProcessW( nullptr, &cmd[0], nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi );
    
        // Get notified when child process ends
        RegisterWaitForSingleObject( &wndData.hWait, pi.hProcess,
            []( PVOID lpParameter, BOOLEAN /*TimerOrWaitFired*/ )
            {
                PostMessage( reinterpret_cast<HWND>(lpParameter), WM_APP, 0, 0 );
            },
            reinterpret_cast<PVOID>(hWnd), INFINITE, WT_EXECUTEONLYONCE );
    
        // Run message loop
        MSG msg;
        while ( GetMessage( &msg, nullptr, 0, 0 ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
    
        // Cleanup
        if( wndData.hWait )
            UnregisterWait( wndData.hWait );
        if( pi.hProcess )
            CloseHandle( pi.hProcess );
        if( pi.hThread )
            CloseHandle( pi.hThread );
    
        return 0;
    }
    

    旧新闻阅读奖励 : Why bother with RegisterWaitForSingleObject when you have MsgWaitForMultipleObjects?

        2
  •  1
  •   catnip    6 年前

    好消息!Windows正是您要查找的API: MsgWaitForMultipleObjects () .

    骗子,是想把这个输入MFC的信息泵,但我发现 this link 建议执行以下操作(代码未经测试,已修复(!),并适应于仅等待一个句柄):

    // virtual
    BOOL CMyApp::PumpMessage()
    {
        DWORD const res = ::MsgWaitForMultipleObjects
            (1, &handle_I_am_interested in, TRUE, INFINITE, QS_ALLINPUT);
    
        switch (res)
        {
            case WAIT_OBJECT_0 + 0:
                // the handle was signalled, strut your stuff here
                return TRUE;
    
            case WAIT_OBJECT_0 + 1:
                // there is a message in the queue, let MFC handle it
                return __super::PumpMessage();
        }
    
        // Shouldn't happen
        return TRUE;
    }
    

    我不得不说,这段代码在我看来仍然不够理想,但可能已经足够接近了。我对MFC的了解不够,无法进一步发表评论。

    请注意: 在mfc通过消息泵之前,此代码将看不到句柄已发出信号。那可能发生在 MessageBox() 例如,有控制权。如果你觉得不舒服,可以考虑使用 RegisterWaitForSingleObject 相反,正如传说中的 Raymond Chen .

        3
  •  0
  •   Jodocus    6 年前

    如果您可以修改子进程的代码,您可以只添加一个back通道,它将通过 SendMessage 当它要离开的时候。如果您不能这样做,您可以创建一个中继进程,该进程将只通过原始子进程数据(如果有的话),但将在子进程离开时执行信息工作。当然,这至少比使用专用线程和例如 WaitForSingleObject .

        4
  •  0
  •   yzt    6 年前

    我要做这两件事之一:

    1. 任一呼叫 WaitForSingleObject() 或者在消息循环的某个地方超时为零(可能必须将循环更改为 PeekMessage() 或添加 WM_TIMER 确保您经常查看的消息,)

    2. 或者更好的方法是,用一个非常小的堆栈生成一个线程(您可以在 CreateThread() call)只等待此子进程,然后将消息发送到您的消息循环。

    我更喜欢选项2,因为一个只有一个小堆栈的线程除了等待东西什么也不做,很难消耗资源。

        5
  •  0
  •   RonTLV    6 年前

    解决方案是在创建应用程序时创建线程。然后等待一个在需要时应该脉冲的事件。例子:

    BOOL bStatus = TRUE;
    CEvent mEvevnt;
    
    // thread function
    UINT LaunchThread( LPVOID p )
    {
        while(bStatus && ::WaitForSingleObject(HANDLE(mEvevnt), INFINITE) == WAIT_OBJECT_0) {
                // create procees here
        }
    }
    
    // thread creation
    AfxBeginThread(LaunchThread, NULL);
    

    触发线程进入操作:

    mEvevnt.PulseEvent();
    

    应用程序结束时销毁线程:

    bStatus = FALSE;
    mEvevnt.PulseEvent();