繁体   English   中英

线程无法在应用程序退出时退出-C ++

[英]Thread Fails to Exit On Application Exit - C++

我的应用程序创建了一个轮询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消息,则返回值为零。

请注意,无论您为wMsgFilterMin和wMsgFilterMax指定哪个值,GetMessage始终会检索WM_QUIT消息。

如果是这种情况,那么我希望对GetMessage的调用检索WM_QUIT消息返回0。但是,调试使我相信该消息未正确接收。 奇怪的是,我可以在WndProc函数中放置一个断点,并且似乎得到了WM_QUIT消息。

我究竟做错了什么? 我是否应该使用其他函数在线程之间发布消息? 谢谢。

这是完整的答案(我几乎可以肯定):

更换

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

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

在您的WindowsProcedure中:

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

 case WM_DESTROY : PostQuitMessage( 0 ); 

虽然我对WinAPI的了解是有限的,但似乎WM_QUIT是特殊的,并不像其他消息一样被发布。

根据Raymond Chen的说法:

像WM_PAINT,WM_MOUSEMOVE和WM_TIMER消息一样,WM_QUIT消息也不是“真实的”已发布消息。 相反,它是系统生成的那些消息之一,就好像它不是已发布一样。

当线程调用PostQuitMessage时,将在队列状态中设置一个标志,该标志表示:“如果有人请求一条消息,并且没有发布的消息,则生成WM_QUIT消息。” 就像其他“虚拟发布”消息一样。

PostThreadMessage只是将消息放置在线程队列中(实际的,不是虚拟的),因此它没有得到真正的PostQuitMessage触发的任何特殊处理。

因此,您可能应该使用PostQuitMessage。

当然,可能有一些方法可以解决当前的奇怪行为(根据其他答案)。 但是,由于WM_QUIT的描述很特殊,因此您可能仍想使用PostQuitMessage。

只是一个猜测。 您的退出代码是从另一个具有自身消息泵的窗口的消息循环调用中调用的? 根据MSDN中的WaitForSingleObject,您可以无限期地阻止当前UI线程,并防止处理自己的消息。

来自http://msdn.microsoft.com/zh-cn/library/ms687032%28VS.85%29.aspx

调用等待函数和直接或间接创建窗口的代码时请小心。 如果线程创建任何窗口,则它必须处理消息。 消息广播被发送到系统中的所有窗口。 使用没有超时间隔的等待功能的线程可能会导致系统死锁。 间接创建窗口的两个代码示例是DDE和CoInitialize函数。 因此,如果您有创建窗口的线程,请使用MsgWaitForMultipleObjects或MsgWaitForMultipleObjectsEx,而不要使用WaitForSingleObject。

WM_Quit消息可能会广播到您自己的窗口,由于您的WaitForSingleObject调用,该窗口不会处理任何消息。 尝试改用MsgWaitForMultipleOjbects,它会不时调用您的消息循环。

您的Alois Kraus

WM_QUIT不是窗口消息,因此您不应该将其发送到窗口。 尝试改用PostThreadMessage

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

如果这样不起作用,请尝试在您的窗口中发布一条虚拟消息:

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

并将其用作退出窗口过程的信号:

case WM_APP:
  PostQuitMessage(0);

您的GetMessage(...)仅检索您提供的窗口的消息。 但是,WM_QUIT没有与之关联的窗口。 您需要在没有窗口句柄(即NULL)的情况下调用GetMessage,该句柄将检索消息队列中的任何消息。

详细说明TheUndeadFish的内容; 调用PostQuitMessage时,在线程的消息队列的唤醒标志中设置了一个“秘密”未记录的QS_QUIT标志。 GetMessage按特定顺序查看其唤醒标志,以确定接下来要处理的消息。

如果发现设置了QS_QUIT ,它将生成WM_QUIT消息并使GetMessage返回FALSE。

您可以获得当前使用GetQueueStatus设置的线程记录的唤醒标志。

有关详细信息,请参见《 Microsoft Windows编程应用程序》第4版(不幸的是,最新版本删除了这些主题)。

此代码有2个问题。

  1. ::GetMessage()不会停止,因为您将hWnd参数与NULL以外的其他参数一起使用。 您需要获取线程消息以获取::GetMessage()以返回0
  2. 按照(1)中的逻辑,您需要使用::PostThreadMessage()发布消息以将其放入线程的消息队列中。

::PostQuitMessage(status)是以下各项的简写:

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

编辑

似乎人们已经开始想到::PostThreadMessage(...,WM_QUIT,...); 不起作用,因为它没有对设置::PostQuitMessage()设置的QS_QUIT标志进行特殊处理。 如果真是这样,那么就不可能将WM_QUIT到另一个线程的消息队列。 这证明它仍然有效。

特别要注意常量Use_PostQuitMessageGetMessage_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(),...);的单线程使用::PostThreadMessage(::GetCurrentThreadId(),...); 调用::background(0); 在主而不是启动线程。

暂无
暂无

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

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