Ожидание на ручке в Windows Thread - PullRequest
0 голосов
/ 08 июня 2018

У меня есть приложение MFC, которое запускает другой процесс, используя CreateProcess(...).Я хотел бы выполнить обновление пользовательского интерфейса, когда созданный процесс завершается.Обычно я бы использовал WaitForSingleObject или WaitForMutlipleObject в возвращаемом процессе HANDLE, но это заблокирует поток GUI (плохо).

Единственное решение, которое я могу придумать, - создать новый поток, которыйможет подождать на ручке и опубликовать сообщение, когда процесс завершается.Это не идеально.

Так можно ли зарегистрировать дескриптор в диспетчере Windows и получать сообщение Windows, когда процесс завершается?

Ответы [ 5 ]

0 голосов
/ 10 июня 2018

Вы можете использовать RegisterWaitForSingleObject(), чтобы получать уведомления с помощью обратного вызова, когда процесс завершится.Функция RegisterWaitForSingleObject направляет поток ожидания в пуле потоков для ожидания процесса, поэтому это должно быть оптимальным использованием ресурсов.Как прокомментировал Раймонд Чен:

Пул потоков может объединять несколько запросов Wait в один вызов WaitForMultipleObjects, поэтому амортизируемая стоимость составляет 1/63 потока.

AНиже приведен минимальный пример приложения с графическим интерфейсом Win32.Код создает окно, затем создает другой экземпляр себя как дочерний процесс, который указывается параметром "/ child".Он регистрирует функцию обратного вызова ожидания и запускает обычный цикл обработки сообщений.Вы можете изменить размер и переместить окно, чтобы увидеть, что графический интерфейс не заблокирован.Когда дочерний процесс завершился, система асинхронно вызывает обратный вызов ожидания, который отправляет определенное приложением сообщение (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;
}

Bonus OldNewThing read : Зачем беспокоиться о RegisterWaitForSingleObject, когдау вас есть MsgWaitForMultipleObjects?

0 голосов
/ 10 июня 2018

Решением является создание потока при создании вашего приложения.Затем вы ждете события, которое должно быть пульсирующим при необходимости.Пример:

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();
0 голосов
/ 08 июня 2018

Я бы сделал одно из следующих двух действий:

  1. Либо вызовите WaitForSingleObject(), либо что-то еще с таймаутом в нуле в вашем цикле сообщений (возможно, придется изменить цикл на PeekMessage() или добавьте WM_TIMER сообщений, чтобы проверять их так часто,)

  2. Или, что еще лучше, порождайте поток с очень маленьким стеком (вы можете настроить его в CreateThread() call), который ждет только этого дочернего процесса, а затем отправляет сообщение в ваш цикл сообщений.

Мне больше нравится вариант 2, поскольку поток с небольшим стеком ничего не делает, кроме ожиданиядля вещи вряд ли истощение ресурсов.

0 голосов
/ 08 июня 2018

Хорошие новости!Windows имеет именно тот API, который вы ищете: MsgWaitForMultipleObjects () .

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

Я должен сказать, что этот код все еще не выглядит идеальным для меня, но этонаверное, достаточно близко.Я не знаю достаточно о MFC, чтобы комментировать дальше.

Обратите внимание: Этот код не увидит, что дескриптор был сигнализирован, пока MFC не пройдет через насос сообщений.Это может произойти, например, когда MessageBox() имеет контроль.Если это вас беспокоит, рассмотрите возможность использования RegisterWaitForSingleObject вместо этого, как рекомендовано выше легендарным Раймондом Ченом .

0 голосов
/ 08 июня 2018

Если вы можете изменить код дочернего процесса, вы можете просто добавить обратный канал, который сообщит родительскому процессу SendMessage, когда он собирается выйти.Если вы не можете этого сделать, вы можете создать процесс ретрансляции, который будет просто передавать исходные данные дочернего процесса (если есть), но будет выполнять информационную работу, когда дочерний процесс уйдет.Это, конечно, наименее мягко говоря, чем просто использование выделенного потока и, например, WaitForSingleObject.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...