与标题一样,我想在工作线程中添加/删除从WTL CListViewCtrl类派生的类,但总是得到"抛出未处理的异常:读取访问冲突。"
我尝试了Win32 API PostMessage 和 SendMessage 但是一旦工作线程接触到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);
}
我尝试使用 new 和 HeapAlloc / LocalAlloc 创建互斥锁和队列,但同样再次出现相同的异常。
我尝试过Win32 API CreateMutex ,但是从工作线程访问互斥锁句柄时没有运气,同样的例外。
当我从GUI线程添加项目时,它工作正常。
如果我将HWND或mutex和队列声明为 static / global ,那么它只能从工作线程工作,但我会避免这种情况,因为我想从这个listcontrol中使用多个实例,我更喜欢任何比全局变量更优雅。
我想让这个类可以重复使用,因为我想多次使用它并进行一些修改(更多列,不同颜色)。
我感谢任何帮助和想法,我将如何使这项工作。
环境: VS2015社区,WTL / C ++和Win10 Pro 64位
我发现导致访问冲突异常的问题: 我在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;
}
// ...
可以从GUI和工作线程调用SafeAddItem 成员函数
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项。我将此指针传递给 _beginthreadex
中的 ThreadProcm_ListCtrl.SafeAddItem(item);
答案 0 :(得分:1)
问题似乎并非真正涉及工作线程的UI更新,而是关于工作线程本身的正确使用。
关于进行UI更新的危险有足够的评论:它们都是潜在的死锁问题。大多数更新都涉及发送消息,这是一个阻塞API调用。当您从工作线程执行更新并且调用线程被阻止时,来自UI中的处理程序的任何尝试与工作程序同步或以其他方式协同工作可能会导致死锁。解决这个问题的唯一方法是在工作线程中准备更新并发出UI线程信号(包括通过发布消息而不是发送消息,以SendMessage
,PostMessage
API)来接管和完成来自UI线程的更新。
回到原始问题:您似乎遇到了静态线程过程的问题。 The fourth argument in the CreateThread
call is:
lpParameter [in,optional]
指向要传递给线程的变量的指针。
您拥有它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
:您拥有_beginthreadex
和AtlCreateThread
(related question)。
答案 1 :(得分:0)
在Windows中,您不应该通过工作线程直接修改GUI控件。在.NET世界中,如果我们想通过工作线程更新控件,我们必须在Delegate上进行平台调用,它基本上执行上下文切换。
您在WIN32中遇到类似的问题。
有一篇关于这个主题的优秀文章我会引起你的注意。它还讨论了各种安全的解决方法: https://www.codeproject.com/Articles/552/Using-Worker-Threads
工作线程和GUI II:不要触摸GUI
“那是对的。工作线程不能触及GUI对象。这意味着你不应该查询控件的状态,向列表框添加内容,设置控件的状态等等。
为什么?
因为你可能陷入严重的僵局。一个经典的例子发布在其中一个讨论板上,它描述了去年发生在我身上的事情。情况是这样的:你启动一个线程,然后决定等待线程完成。同时,线程做了一些看似无害的事情,例如在列表框中添加内容,或者在发布的示例中调用FindWindow。在这两种情况下,由于所有线程都陷入僵局,这个过程突然停止了。“