0
UINT __stdcall CExternal::WorkThread( void * pParam)
{
    HRESULT hr;
    CTaskBase* pTask;
    CComPtr<IHTMLDocument3> spDoc3;
    CExternal* pThis = reinterpret_cast<CExternal*>(pParam);

    if (pThis == NULL)
        return 0;

    // Init the com
    ::CoInitializeEx(0,COINIT_APARTMENTTHREADED);
    hr = ::CoGetInterfaceAndReleaseStream(
        pThis->m_pStream_,
        IID_IHTMLDocument3,
        (void**)&spDoc3);

    if(FAILED(hr))
        return 0;

    while (pThis->m_bShutdown_ == 0) 
    {
        if(pThis->m_TaskList_.size()) 
        {
            pTask = pThis->m_TaskList_.front();
            pThis->m_TaskList_.pop_front();

            if(pTask) 
            {
                pTask->doTask(spDoc3); //do my custom task
                delete pTask;
            }
        } 
        else
        {
            Sleep(10);
        }
    }

    OutputDebugString(L"start CoUninitialize\n");
    ::CoUninitialize(); //release com
    OutputDebugString(L"end CoUninitialize\n");
    return 0;
}

上面让我的线程挂起的代码,唯一的输出是“start CoUninitialize”。

m_hWorker_ = (HANDLE)_beginthreadex(NULL, 0, WorkThread, this, 0, 0);

这段代码启动了我的线程,但是线程无法安全退出,所以它等待。这段代码有什么问题?

4

1 回答 1

2

问题不在此代码中,尽管它违反了核心 COM 要求。这表示当您不再使用接口指针时应该释放它们,调用 IUnknown::Release(),并且单元线程线程必须泵送消息循环。尤其是消息循环很重要,当单线程对象(如浏览器)的所有者线程没有抽水时,您将陷入死锁。

CoUninitialize() 被迫清理 spDoc3 包装的接口指针,因为您自己没有这样做。从代码中可以清楚地看出,接口指针的所有者实际上是在另一个线程上运行的,通常要记住这一点,因为这几乎违背了启动自己的工作线程的意义。创建自己的 STA 线程并不能解决此问题,它仍然是错误的线程。

所以代理需要上下文切换到拥有浏览器对象的单元。这个单元的硬性要求是泵送一个消息循环,以便可以在正确的线程上分派调用,以便安全地调用 Release() 函数。当您的程序关闭时,该线程不再发送消息的可能性非常高。您应该能够在调试器中看到的东西,在 Debug + Windows + Threads 窗口中找到所有者线程并查看它在做什么。

死锁是常见的结果。修复它的唯一好方法是以正确的顺序关闭线程,这个必须在拥有浏览器对象的线程之前关闭。当线程具有这样的相互依赖关系时,干净地关闭多线程程序可能非常困难。C++11 std::quick_exit() 添加背后的灵感。

于 2013-03-25T11:19:56.723 回答