c++ - 在 WTL 和 C++ 中从工作线程更新 CListViewCtrl

标签 c++ multithreading user-interface wtl

如标题所示,我想从工作线程向从 WTL CListViewCtrl 类派生的类添加/删除项目,但总是得到“抛出未处理的异常:读取访问冲突。”

我尝试了 Win32 API PostMessageSendMessage 但是一旦工作线程接触到 CListViewCtrl 的 HWND,我就会得到同样的异常。

// CListCtrl member function, calling from worker thread
HWND GetHwnd()
{
    return hwndListCtrl;       // exception here
}

我试过这个 SafeQueue但是一旦工作线程接触到互斥量或队列,就会再次发生异常。

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

我尝试使用 newHeapAlloc/LocalAlloc 创建互斥锁和队列,但再次出现相同的异常。

我尝试了 Win32 API CreateMutex 但没有成功,从工作线程访问互斥锁句柄时出现同样的异常。

当我从 GUI 线程添加项目时它工作正常。

如果我将 HWND 或互斥锁和队列声明为 static/global ,它只能在工作线程中工作,但我会避免这种情况,因为我想使用此 listcontrol 中的多个实例并且我更喜欢任何比全局变量更优雅的方式。

我想让这个类可重用,因为我想通过一些修改(更多列、不同颜色)多次使用它。

我很感激任何帮助以及我如何使这项工作的想法。

环境: VS2015 社区、WTL/C++ 和 Win10 Pro 64bit

我发现导致访问冲突异常的问题: 我在 CListViewCtrl 类中将 ThreadProc 回调函数声明为静态成员函数

// DO NOT USE
// in CListViewCtrl
**static** DWORD WINAPI ThreadProc(LPVOID lp)
{
. . .
}

LRESULT OnStartWorkerThread(WORD /*wNotifyCode*/, WORD /*wID*/, HWND . ..)
{
    DWORD dw;
    ::CreateThread(NULL, 0, this->ThreadProc, NULL, 0, &dw);
}

可行的解决方案:

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;

    // . . .

检查是否从 GUI 或任何其他线程调用了 SafeAddItem 函数

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

        return true;
    }

    // ...

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

    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);
     }
    // . . .

PostMessage 的消息处理程序,我们在 GUI 线程中

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

现在我们可以通过这种方式从任何线程添加 listctrl 项目。我将 this 指针传递给 _beginthreadex

中的 ThreadProc
m_ListCtrl.SafeAddItem(item);

最佳答案

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

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

回到最初的问题:您似乎遇到了静态线程过程的问题。 The fourth argument in the CreateThread call is :

lpParameter [in, optional]

A pointer to a variable to be passed to the thread.

你有它 NULL 并且你通常使用它来将 this 值传递给你的线程过程回调。这样您就可以将执行从静态函数传回给您的类实例:

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:您有 _beginthreadexAtlCreateThread ( related question )。

关于c++ - 在 WTL 和 C++ 中从工作线程更新 CListViewCtrl,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42268177/

相关文章:

c++ - 当我读取一个大小为 17 mb 的 100 万个 url 的文件时,我的程序占用了 163 mb 的大小

c++ - 是否有可能检测到线程在 Linux [暂停] 中进行了上下文切换?

c# - Thread.Sleep 是在 C# 中实现我自己的 Timer 的正确方法吗?

c# - 如何用另一个 DocumentText 更新 DocumentText

c++ - GUI 和文本模式 C++ 设计以消除冗余(可选参数?函数重载?)

c++ - C++/内联汇编中的运行时修补

c++ - 使用 C++ MFC 进行递归文件搜索?

python - 如何在 tkinter 中安排更新(f/e,更新时钟)?

android - 就 UI 而言, native Activity 与 SDK Activity

c++ - 构造函数初始化列表 - 这行得通吗?