C++ 从WTL和C+中的工作线程更新CListViewCtrl+;

C++ 从WTL和C+中的工作线程更新CListViewCtrl+;,c++,multithreading,user-interface,wtl,C++,Multithreading,User Interface,Wtl,正如在标题中一样,我希望从工作线程向从WTL CListViewCtrl类派生的类添加/删除项,但始终获得“未处理的异常抛出:读取访问冲突” 我尝试了Win32 APIPostMessage和SendMessage,但一旦工作线程接触到CListViewCtrl的HWND,我就会得到相同的异常 // CListCtrl member function, calling from worker thread HWND GetHwnd() { return hwndListCtrl;

正如在标题中一样,我希望从工作线程向从WTL CListViewCtrl类派生的类添加/删除项,但始终获得“未处理的异常抛出:读取访问冲突”

我尝试了Win32 APIPostMessageSendMessage,但一旦工作线程接触到CListViewCtrl的HWND,我就会得到相同的异常

// CListCtrl member function, calling from worker thread
HWND GetHwnd()
{
    return hwndListCtrl;       // exception here
}
我尝试了这个方法,但一旦工作线程触及互斥对象或队列,就会再次出现异常

// SafeQueue is member variable in CListViewCtrl, created in GUI thread
SafeQueue<T> m_SafeQueue;
. . .
// member function in SafeQueue class, calling from worker thread
void enqueue(T t)
{
    std::lock_guard<std::mutex> lock(m);  // exception here
    q->push(t);
}
一个有效的解决办法:

class CListViewCtrl ...
{
    // thread-safe queue to store listctrl items to be added later in GUI thread
    SafeQueue<CListCtrlItem<nCols> > m_SafeQueue;  

    // thread ID of the thread in which listctrl was created, saved in OnCreate
    DWORD m_dwGuiTid;

    // . . .
SafeAddItem可以从GUI和工作线程调用成员函数

    BOOL InvokeRequired()
    {
        if (m_GuiTid == ::GetCurrentThreadId())
            return false;

        return true;
    }

    // ...
    void SafeAddItem(CListCtrlItem<nCols> item)
    {
        if (!InvokeRequired())
        {
            // we are in GUI thread so just add listctrl item "normal" way
            AddItem(item);
            return;
        }

     // we are in other thread so enqueue listctrl item and post a message to GUI           
        m_SafeQueue.Enqueue(item);
        ::PostMessage(m_hWnd, WM_ADD_ITEM, 0, 0);
     }
    // . . .

在Windows中,决不能通过工作线程直接修改GUI控件。在.NET世界中,如果我们想通过工作线程更新控件,我们必须对委托执行平台调用,该委托基本上执行上下文切换

在WIN32中也有类似的问题

有一篇关于这个问题的优秀文章,我将提请你注意。它还讨论了各种安全解决方法:

工作线程和GUI II:不要触摸GUI

“是的。工作线程不得接触GUI对象。这意味着您不应查询控件的状态、向列表框添加内容、设置控件的状态等

为什么?


因为你可能会陷入严重的死锁状态。一个经典的例子发布在一个讨论板上,它描述了去年发生在我身上的事情。这种情况是:你启动一个线程,然后决定等待线程完成。与此同时,线程做了一些显然无害的事情,比如添加s列表框中的某个内容,或者,在发布的示例中,调用FindWindow。在这两种情况下,进程都会突然停止,因为所有线程都死锁。”

问题似乎不是关于工作线程的UI更新,而是关于工作线程本身的正确使用

关于UI更新的危险性有足够多的评论:它们都是关于潜在的死锁问题。大多数更新都涉及发送消息,这是一个阻塞API调用。当您从工作线程执行更新并且调用线程被阻止时,UI中的处理程序试图与工作线程同步或以其他方式协同工作的任何尝试都可能导致死锁。解决此问题的唯一方法是在工作线程中准备更新,并向UI线程发出信号(包括通过发布消息而不是发送消息,
sendmages
PostMessage
API)从UI线程接管并完成更新

回到原始问题:您似乎对静态线程过程有问题:

lpParameter[输入,可选]

指向要传递给线程的变量的指针

您有它
NULL
,通常使用它将
值传递给线程过程回调。通过这种方式,您可以将执行从静态函数传递回类实例:

DWORD CFoo::ThreadProc()
{
    // ThreadProc with proper "this" initialization
    // HWND h = GetHwnd()...
}
DWORD WINAPI ThreadProc(LPVOID pvParameter)
{
    return ((CFoo*) pvParameter)->ThreadProc();
}
LRESULT CFoo::OnStartWorkerThread(WORD /*wNotifyCode*/, WORD /*wID*/, HWND ...)
{
    DWORD dw;
    ::CreateThread(NULL, 0, this->ThreadProc, (LPVOID) this, 0, &dw);
}

还要注意,您不应该直接使用
CreateThread
:您有
\u beginthreadex
AtlCreateThread
()。

谢谢您的回复。我知道我不应该从其他线程访问GUI对象,但我甚至不能访问互斥句柄,这对于线程之间的同步非常重要。从其他线程访问数据成员不应该导致访问冲突异常,只是奇怪的行为。我觉得这是一个奇怪的问题,看起来我发现了问题:我在CListViewCtrl中将工作线程回调函数声明为静态成员函数。静态成员函数只能访问静态数据成员。感谢您的澄清。根据您的描述-“我想从工作线程向从WTL CListViewCtrl类派生的类添加/删除项…我尝试了Win32 API PostMessage和SendMessage,但一旦工作线程接触CListViewCtrl的HWND,我会得到相同的异常…从GUI线程添加项时效果良好。”听起来好像您正试图直接从工作线程访问ListView。如果是误解,请道歉。首先,我尝试使用PostMessage,但它的第一个参数是目标窗口HWND,但我无法从工作线程读取/获取此HWND,因为它导致访问冲突异常。这对我来说听起来很奇怪,因为这不应该引起异常。现在,它与非成员非静态ThreadProc函数一起正常工作。感谢您解释您的解决方案。感谢您的建议。我知道SendMessage是一个阻塞调用,所以我想改用PostMessage,但由于这是一个非常奇怪的问题,我尝试了各种解决方案以使其正常工作。我用一个有效的解决方案更新了我的问题。我使用该安全队列临时存储listctrl项,因为在PostMessageWParam或lParam中跨线程发送指针会更复杂。
    LRESULT OnAddItem(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
    {
        CListCtrlItem<nCols> item;
        while (!m_SafeQueue.Empty())
        {
            item = m_SafeQueue.Dequeue();
            // we are in GUI thread so we can add list ctrl items normal way
            AddItem(item);
        }
        return 1;
    }
    // ...
}
m_ListCtrl.SafeAddItem(item);
DWORD CFoo::ThreadProc()
{
    // ThreadProc with proper "this" initialization
    // HWND h = GetHwnd()...
}
DWORD WINAPI ThreadProc(LPVOID pvParameter)
{
    return ((CFoo*) pvParameter)->ThreadProc();
}
LRESULT CFoo::OnStartWorkerThread(WORD /*wNotifyCode*/, WORD /*wID*/, HWND ...)
{
    DWORD dw;
    ::CreateThread(NULL, 0, this->ThreadProc, (LPVOID) this, 0, &dw);
}