Поток не может выйти при выходе из приложения - C ++ - PullRequest
1 голос
/ 02 декабря 2010

Мое приложение создает поток, который опрашивает сообщения Windows.Когда приходит время закрыться, мое приложение отправляет сообщение WM_QUIT.

В потоке приложения я пытаюсь завершить работу:

if ( _hNotifyWindowThread != NULL )
{
    ASSERT(_pobjNotifyWindow != NULL);

    ::SendMessage( _pobjNotifyWindow->m_hWnd, WM_QUIT, 0, 0 );
    ::WaitForSingleObject( _hNotifyWindowThread, 50000L );
    ::CloseHandle( _hNotifyWindowThread ); // <-- PC never gets here.
    _hNotifyWindowThread = NULL;
}

Этонасос сообщений, работающий в моей функции потока:

// Start the message pump...
while ( (bRetVal = ::GetMessage(
    &msg,                           // message structure
    _pobjNotifyWindow->m_hWnd,      // handle to window whose messages are to be retrieved
    WM_DEVICECHANGE,                // lowest message value to retrieve
    WM_DEVICECHANGE                 // highest message value to retrieve
    )) != 0 )
{
    switch ( bRetVal )
    {
    case -1:                        // Error generated in GetMessage.
        TRACE(_T("NotifyWindowThreadFn : Failed to get notify window message.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
        return ::GetLastError();
        break;

    default:                        // Other message received.
        ::TranslateMessage( &msg );
        ::DispatchMessage( &msg );
        break;
    }
}

delete _pobjNotifyWindow;           // Delete the notify window.

return msg.wParam;                  // Return exit code.

Документация Microsoft для GetMessage гласит:

Если функция получает сообщение WM_QUIT,возвращаемое значение равно нулю.

Обратите внимание, что GetMessage всегда извлекает сообщения WM_QUIT, независимо от того, какие значения вы указываете для wMsgFilterMin и wMsgFilterMax.

Если это так, то я ожидаю вызова GetMessageэто возвращает сообщение WM_QUIT для возврата 0. Однако отладка заставляет меня поверить, что сообщение не получено должным образом.Странно, что я могу поместить точку останова в функцию WndProc, и она, похоже, получает сообщение WM_QUIT.

Что я делаю не так?Должен ли я использовать другую функцию для публикации сообщений между темами?Спасибо.

Ответы [ 7 ]

2 голосов
/ 02 декабря 2010

Полный ответ (я почти уверен):

заменить

::SendMessage( _pobjNotifyWindow->m_hWnd, WM_QUIT, 0, 0 ); 

на

::PostMessage( _pobjNotifyWindow->m_hWnd, WM_CLOSE, 0, 0 ); 

заменить

 ( (bRetVal = ::GetMessage( &msg, _pobjNotifyWindow->m_hWnd, WM_DEVICECHANGE, WM_DEVICECHANGE )) != 0 ) 

with ( (bRetVal = ::GetMessage( &msg, NULL ,0 ,0 )) != 0 )

В вашей WindowsProcedure:

 case WM_CLOSE : DestroyWindow( hWnd ); break; //can be return 

 case WM_DESTROY : PostQuitMessage( 0 ); 
1 голос
/ 02 декабря 2010

Хотя мои знания о WinAPI имеют ограничения, кажется, что WM_QUIT особенный и не предназначен для публикации, как другие сообщения.

Согласно Рэймонд Чен :

Как и сообщения WM_PAINT, WM_MOUSEMOVE и WM_TIMER, сообщение WM_QUIT не является «реальным» опубликованным сообщением. Скорее, это одно из тех сообщений, которые система генерирует, как если бы она была опубликована, даже если это не так.

.

Когда поток вызывает PostQuitMessage, устанавливается флаг в состоянии очереди, который говорит: «Если кто-то запрашивает сообщение, а опубликованных сообщений нет, то создайте сообщение WM_QUIT». Это так же, как и другие «виртуально размещенные» сообщения.

.

PostThreadMessage просто помещает сообщение в очередь потока (реально, а не виртуально), и, следовательно, он не получает какой-либо специальной обработки, которую вызывает настоящий PostQuitMessage.

Так что вы, вероятно, должны использовать PostQuitMessage.

Конечно, могут быть способы обойти текущее странное поведение (согласно другим ответам). Но, учитывая специальное описание WM_QUIT, вы можете в любом случае использовать PostQuitMessage.

0 голосов
/ 02 декабря 2010

Есть 2 проблемы с этим кодом.

  1. ::GetMessage() не останавливается, потому что вы используете параметр hWnd с чем-то другим, чем NULL.Вам нужно получить сообщения thread , чтобы получить ::GetMessage() для возврата 0.
  2. Следуя логике в (1), вам нужно опубликовать сообщение, используя ::PostThreadMessage() дляпоместите его в очередь сообщений потока.

Все это довольно хорошо иллюстрируется тем фактом, что ::PostQuitMessage(status) является сокращением для

::PostThreadMessage(::GetCurrentThreadId(), WM_QUIT, status, 0);

EDIT :

Кажется, что людей заставили думать, что ::PostThreadMessage(...,WM_QUIT,...); не работает, потому что он не получает специальной обработки установки флага QS_QUIT, установленного ::PostQuitMessage().Если бы это было так, то не было бы никакого способа отправить WM_QUIT в очередь сообщений другого потока.Вот доказательство того, что оно работает в любом случае.

В частности, обратите внимание на константы Use_PostQuitMessage и GetMessage_UseWindowHandle.Не стесняйтесь изменять значения и поиграть с кодом.Он работает так же, как указано в моем ответе, за исключением того, что я по ошибке использовал ::GetCurrentThread() вместо ::GetCurrentThreadId(), прежде чем попробовать.

#include <Windows.h>
#include <iomanip>
#include <iostream>

namespace {

        // Doesn't matter if this is 'true' or 'false'.
    const bool Use_PostQuitMessage        = false;

        // Setting this to 'true' prevents the application from closing.
    const bool GetMessage_UseWindowHandle = false;

    void post_quit_message ()
    {
        if ( Use_PostQuitMessage ) {
            ::PostQuitMessage(0);
        }
        else {
            ::PostThreadMessageW(::GetCurrentThreadId(), WM_QUIT, 0, 0);
        }
    }

    ::BOOL get_message ( ::HWND window, ::MSG& message )
    {
        if ( GetMessage_UseWindowHandle ) {
            return (::GetMessageW(&message, window, 0, 0));
        }
        else {
            return (::GetMessageW(&message, 0, 0, 0));
        }
    }

    ::ULONG __stdcall background ( void * )
    {
            // Allocate window in background thread that is to be interrupted.
        ::HWND window = ::CreateWindowW(L"STATIC", 0, WS_OVERLAPPEDWINDOW,
            0, 0, 512, 256, 0, 0, ::GetModuleHandleW(0), 0);
        if ( window == 0 ) {
            std::cerr << "Could not create window." << std::endl;
            return (EXIT_FAILURE);
        }

            // Process messages for this thread's windows.
        ::ShowWindow(window, SW_NORMAL);
        ::MSG message;
        ::BOOL result = FALSE;
        while ((result = get_message(window,message)) > 0)
        {
                // Handle 'CloseWindow()'.
            if ( message.message == WM_CLOSE )
            {
                post_quit_message(); continue;
            }
                // Handling for 'ALT+F4'.
            if ((message.message == WM_SYSCOMMAND) &&
                (message.wParam == SC_CLOSE))
            {
                post_quit_message(); continue;
            }
                // Dispatch message to window procedure.
            ::TranslateMessage(&message);
            ::DispatchMessageW(&message);
        }
            // Check for error in 'GetMessage()'.
        if ( result == -1 )
        {
            std::cout << "GetMessage() failed with error: "
                << ::GetLastError() << "." << std::endl;
            return (EXIT_FAILURE);
        }
        return (EXIT_SUCCESS);
    }

}

int main ( int, char ** )
{
        // Launch window & message pump in background thread.
    ::DWORD id = 0;
    ::HANDLE thread = ::CreateThread(0, 0, &::background, 0, 0, &id);
    if ( thread == INVALID_HANDLE_VALUE ) {
        std::cerr << "Could not launch thread." << std::endl;
        return (EXIT_FAILURE);
    }

        // "do something"...
    ::Sleep(1000);

        // Decide to close application.
    ::PostThreadMessageW(id, WM_QUIT, 0, 0);

        // Wait for everything to shut down.
    ::WaitForSingleObject(thread, INFINITE);

        // Return background thread's success code.
    ::DWORD status = EXIT_FAILURE;
    ::GetExitCodeThread(thread,&status);
    return (status);
}

PS :

Чтобы на самом деле протестировать однопоточное использование ::PostThreadMessage(::GetCurrentThreadId(),...);, вызовите ::background(0); в main вместо запуска потока.

0 голосов
/ 02 декабря 2010

Чтобы уточнить, что сказал TheUndeadFish;есть "секретный" недокументированный флаг QS_QUIT, установленный в флагах пробуждения очереди сообщений потока, когда вызывается PostQuitMessage.GetMessage просматривает свои флаги пробуждения в определенном порядке, чтобы определить, какое сообщение обрабатывать следующим.

Если он обнаруживает, что установлено значение QS_QUIT, он генерирует сообщение WM_QUIT и заставляет GetMessage возвращать FALSE.

Вы можете получить задокументированные флаги пробуждения потока, которые в настоящее время установлены с GetQueueStatus .

Подробности можно найти в Прикладные программыдля Microsoft Windows , 4-е издание (к сожалению, новейшая редакция удалила эти темы).

0 голосов
/ 02 декабря 2010

Ваш GetMessage (...) извлекает только сообщение из окна, которое вы указали.Однако WM_QUIT не имеет связанного с ним окна.Вам нужно вызвать GetMessage без дескриптора окна (т.е. NULL), который извлекает любое сообщение в очереди сообщений.

0 голосов
/ 02 декабря 2010

WM_QUIT не является оконным сообщением, поэтому не следует отправлять его в окно. Попробуйте использовать PostThreadMessage вместо:

PostThreadMessage(GetThreadId(_hNotifyWindowThread), WM_QUIT, 0, 0);

Если это не сработает, попробуйте отправить в окно фиктивное сообщение:

::PostMessage( _pobjNotifyWindow->m_hWnd, WM_APP, 0, 0 );

и используйте его как сигнал для выхода из вашей оконной процедуры:

case WM_APP:
  PostQuitMessage(0);
0 голосов
/ 02 декабря 2010

Просто предположение. Ваш код выхода вызывается из вызова цикла сообщений из другого окна, которое имеет свой собственный насос сообщений? Согласно MSDN для WaitForSingleObject, вы блокируете текущий поток пользовательского интерфейса на неопределенный срок и предотвращаете обработку его собственных сообщений.

С http://msdn.microsoft.com/en-us/library/ms687032%28VS.85%29.aspx

Соблюдайте осторожность при вызове ожидания функции и код, который напрямую или косвенно создает окна. Если поток создает любые окна, он должен обрабатывать сообщения. Сообщения трансляций отправляются на все окна в системе. Поток, который использует функцию ожидания без времени ожидания может привести к система зашла в тупик. Два примеры кода, который косвенно создает окна DDE и Коинициализировать функцию. Следовательно, если у вас есть нить, которая создает окна, используйте MsgWaitForMultipleObjects или MsgWaitForMultipleObjectsEx, скорее чем WaitForSingleObject.

Возможно, сообщение WM_Quit транслируется в ваше собственное окно, которое не обрабатывает никаких сообщений из-за вашего вызова WaitForSingleObject. Вместо этого попробуйте MsgWaitForMultipleOjbects, который время от времени пытается вызвать ваш цикл обработки сообщений.

С уважением, Алоис Краус

...