简体   繁体   English

在Windows线程中等待句柄

[英]Waiting on a handle in Windows Thread

I have an MFC application that launches another process using CreateProcess(...) . 我有一个MFC应用程序,它使用CreateProcess(...)启动另一个进程。 I would like to perform a UI update when the created process terminates. 创建的进程终止时,我想执行UI更新。 Normally, I would use WaitForSingleObject or WaitForMutlipleObject on the returned process HANDLE but this will block the GUI thread (bad). 通常,我将在返回的进程HANDLE上使用WaitForSingleObjectWaitForMutlipleObject ,但这将阻塞GUI线程(错误)。

The only solution I can think of is to spawn a new thread that can wait on the handle and post a message when the process terminates. 我能想到的唯一解决方案是生成一个新线程,该线程可以等待该句柄并在进程终止时发布消息。 This is not ideal. 这是不理想的。

So is it possible to register the handle with the Windows Manager and receive a Windows message when the process terminates? 因此,可以在进程终止时向Windows Manager注册句柄并接收Windows消息吗?

You can use RegisterWaitForSingleObject() to get notified via callback, when the process has ended. 进程结束后,可以使用RegisterWaitForSingleObject()通过回调获得通知。 The RegisterWaitForSingleObject function directs a wait thread in the thread pool to wait on the process, so this should be optimal usage of resources. RegisterWaitForSingleObject函数将线程池中的等待线程定向为等待进程,因此这应该是资源的最佳用法。 As Raymond Chen commented: 正如雷蒙德·陈(Raymond Chen)所说:

The thread pool can batch multiple Wait requests into a single call to WaitForMultipleObjects so the amortized cost is 1/63 of a thread. 线程池可以将多个Wait请求批处理为对WaitForMultipleObjects的单个调用,因此摊销成本为线程的1/63。

A minimal example of a Win32 GUI application follows. 下面是Win32 GUI应用程序的最小示例。 The code creates a window, then it creates another instance of itself as a child process, which is indicated by the "/child" parameter. 该代码创建一个窗口,然后创建其自身的另一个实例作为子进程,由“ / child”参数指示。 It registers the wait callback function and runs a regular message loop. 它注册了等待回调函数并运行常规的消息循环。 You can resize and move the window to see that the GUI is not blocked. 您可以调整大小和移动窗口,以查看GUI没有被阻止。 When the child process has ended, the system asynchronously calls the wait callback which posts an application-defined message ( WM_APP ) to the window. 子进程结束后,系统异步调用wait回调,该回调将应用程序定义的消息( WM_APP )发布到窗口。 When the window receives the message, it immediately calls UnregisterWait() to cancel the wait. 窗口收到消息后,立即调用UnregisterWait()取消等待。 As the reference states, even wait operations that use WT_EXECUTEONLYONCE must be canceled when the wait is completed (but not from within the callback!). 作为参考状态,即使使用WT_EXECUTEONLYONCE等待操作也必须在等待完成后取消(但不能从回调内部!)。 Then the window shows a message box to demonstrate that it has received the message. 然后,该窗口将显示一个消息框,以表明它已收到该消息。

Error handling is omitted for brevity. 为简洁起见,省略了错误处理。 You should check the return value of each API function and call GetLastError() in case FALSE is returned. 您应该检查每个API函数的返回值,并在返回FALSE情况下调用GetLastError()

#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;
}

Bonus OldNewThing read : Why bother with RegisterWaitForSingleObject when you have MsgWaitForMultipleObjects? Bonus OldNewThing阅读当您拥有MsgWaitForMultipleObjects时,为什么还要麻烦RegisterWaitForSingleObject?

Good news! 好消息! Windows has exactly the API you're looking for: MsgWaitForMultipleObjects () . Windows完全具有您要查找的API: MsgWaitForMultipleObjects()

Tricker, is to get this into MFC's message pump, but I found this link which recommends doing the following (code untested, fixed (!), and adapted to wait on just one handle): Tricker,是为了将其放入MFC的消息泵中,但我发现此链接建议执行以下操作(代码未经测试,固定(!),并且仅等待一个句柄即可):

// 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;
}

I have to say that this code still doesn't look ideal to me, but it's probably close enough. 我不得不说这段代码对我来说仍然不是很理想,但是可能已经足够接近了。 I don't know enough about MFC to comment further. 我对MFC的了解不足,无法进一步发表评论。

Please note: This code won't see that the handle has been signalled until MFC passes through the message pump. 请注意:在MFC通过消息泵之前,此代码不会看到已向该手柄发出信号。 That might happen while MessageBox() has control, for example. 例如,在MessageBox()具有控制权的情况下可能会发生这种情况。 If that bothers you, consider using RegisterWaitForSingleObject instead, as recommended above by the legendary Raymond Chen . 如果这让您感到困扰,请考虑改用RegisterWaitForSingleObject ,如传奇的Raymond Chen所建议的那样。

If you can modify the code of the child process, you may just add a back-channel that will inform the parent process by SendMessage when it is about to leave. 如果可以修改子进程的代码,则可以仅添加一个反向通道,该通道将在SendMessage即将离开时通过SendMessage通知父进程。 If you cannot do that, you may create a relay process that will just pass-through the original child process data (if any) but will do the information job when the child left. 如果您不能这样做,则可以创建一个中继过程,该过程将仅传递原始的子过程数据(如果有),但是在子项离开时将执行信息工作。 This is, of course, least to say far less elegent than just using a dedicated thread and eg WaitForSingleObject . 当然,这至少要比仅使用专用线程和例如WaitForSingleObject少得多。

I would do one of these two: 我将执行以下两个操作之一:

  1. Either call WaitForSingleObject() or whatever with a timeout of zero in your message loop somewhere (might have to change your loop to PeekMessage() or add WM_TIMER messages to make sure you check every so often,) 在某个地方的消息循环中调用WaitForSingleObject()或超时为零的任何东西(可能必须将循环更改为PeekMessage()或添加WM_TIMER消息以确保您经常检查一次,)

  2. Or better still, spawn a thread with a very small stack (you can customize that in the CreateThread() call) that only waits for this child process and then posts a message to your message loop. 或更妙的是,生成一个具有很小堆栈的线程(您可以在CreateThread()调用中自定义线程),该线程仅等待此子进程,然后将一条消息发布到您的消息循环中。

I like option 2 better, since a thread with a small stack that does nothing but wait for stuff is hardly a resource drain. 我更喜欢选项2,因为只有一个小的堆栈的线程除了等待任务外什么都不做,几乎不会浪费资源。

The solution is creating a thread when your app is created. 解决方案是在创建应用程序时创建线程。 You then wait on an event that should be pulsed when needed. 然后,您等待应在需要时触发的事件。 Example: 例:

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);

Trigger thread into action: 触发线程生效:

mEvevnt.PulseEvent();

You destroy the thread when your app is ending: 您在应用结束时销毁线程:

bStatus = FALSE;
mEvevnt.PulseEvent();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM