背景:
前几天做东西用了windows自己的消息队列构建自己的线程,结果出现了丢消息的问题,现在对其进行分析和记录
构建一个消息驱动的消息队列,这样我可以投递不同的消息,只需要在OnThreadMessage中进行消息处理即可;并且OnThreadMessage是一个虚函数,这样可以以他为基类构建不同的处理线程,到时候只需要重写OnThreadMessage即可,这个线程也是从早期的filezillaserver中提取的Thread类;
DWORD CThread::Run()
{
InitInstance();
SetEvent(m_hEventStarted);
m_started = true;
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
if (!msg.hwnd)
OnThreadMessage(msg.message, msg.wParam, msg.lParam);
DispatchMessage(&msg);
}
DWORD res=ExitInstance();
delete this;
return res;
}
刚开始的时候线程还可以正常运行,后来因为要添加同步处理消息,我就给投递同步消息的接口添加了互斥锁,然后直接该线程内部的处理函数,后来发现线程内部调用的一个库(立体血流的库)会崩溃,后来查找原因,是因为这个库编写的不是线程安全的,因此又通过添加锁进行了处理但是还是有问题;但是原来的库在单线程中运行是没有问题的;
原因探究:
鉴于此我又把同步的方式进行了修改,将同步消息也是直接投递,然后等待线程运行完毕,通过事件通知,这个时候我经常发现我会丢消息,并且丢的消息是在处理完调用这个库的消息后面造成发送消息者一直等待,得不到回复;后来查找说PostThreadMessage的消息会不被处理,如果库内部有独立的消息循环,比如模式对话框,或者窗口的创建等;说可以通过PostMessage进行规避;
然后我就开始修改我的线程的消息投递方式,我在线程时初始化的时候创建了一个窗口,并且将窗口过程调用OnThreadMessage,这样感觉就不会丢消息了,
class CAsyncSocketExHelperWindow
{
public:
CAsyncSocketExHelperWindow(CAsyncSocketExHelperWindow *pWnd)
{
//Create window
WNDCLASSEX wndclass;
wndclass.cbSize=sizeof wndclass;
wndclass.style=0;
wndclass.lpfnWndProc=WindowProc;
wndclass.cbClsExtra=0;
wndclass.cbWndExtra=0;
wndclass.hInstance=GetModuleHandle(0);
wndclass.hIcon=0;
wndclass.hCursor=0;
wndclass.hbrBackground=0;
wndclass.lpszMenuName=0;
wndclass.lpszClassName=_T("CAsyncSocketEx Helper Window");
wndclass.hIconSm=0;
RegisterClassEx(&wndclass);
m_hWnd=CreateWindow(_T("CAsyncSocketEx Helper Window"), _T("CAsyncSocketEx Helper Window"), 0, 0, 0, 0, 0, 0, 0, 0, GetModuleHandle(0));
ASSERT(m_hWnd);
SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR)this);
m_pWnd = pWnd;
};
virtual ~CAsyncSocketExHelperWindow()
{
//Destroy window
if (m_hWnd)
{
DestroyWindow(m_hWnd);
m_hWnd=0;
}
}
//Processes event notifications sent by the sockets or the layers
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_USER) //Notification event sent by a layer
{
//Verify parameters, lookup socket and notification message
//Verify parameters
ASSERT(hWnd);
CAsyncSocketExHelperWindow *pWnd=(CAsyncSocketExHelperWindow *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
ASSERT(pWnd);
pWnd->m_pWnd->OnThreadMessage();
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
HWND CAsyncSocketExHelperWindow::GetHwnd()
{
return m_hWnd;
}
private:
HWND m_hWnd;
CAsyncSocketExHelperWindow *m_pWnd
};
结果程序一运行就出现了调用立体血流库不返回并且镶嵌调用导致堆栈溢出;
while (GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
最终原因分析:
后来经过分析应该是立体血流库的内部也使用了消息循环,当他进行立体血流渲染的时候,他为了让线程继续运行应该是添加了对消息队列的的窗口消息处理,这样就造成了
1.在发送窗口消息的情况下
发送调用立体血流的窗口消息->立体血流初始化->立体血流拦截并处理窗口消息->立体血流初始化->立体血流拦截并处理窗口消息,这样的循环嵌套,最终导致程序堆栈溢出。
2.在发送线程消息的情况下
发送线程消息->立体血流初始化->拦截消息队列,不处理线程消息,导致线程消息丢失。
最终的解决办法:
通过事件,互斥锁,队列,构建自己的消息循环来实现消息队列,不能使用windows的消息循环来进行构建基础的消息驱动的消息队列;这样可以规避调用的方法【对框架】造成的问题;
windows的消息队列在设计的时候是考虑了各个模块都可以调佣这样增加了灵活性,任意时刻,任何模块都可以和他进行交互;
但是如果我们的线程框架规定好了一个执行顺序,不允许随意更改,那么最好不要使用windows的消息队列。
结束:
在很多项目中我都是看见线程的消息循环是自己构建的,当时我还很奇怪为什么不用windows的消息循环,经历此次教训,我明白了。