模板应用--UI线程与worker线程同步 模仿c# invoke
2013-08-27 18:20
429 查看
由之前的一篇博文 《UI线程与worker线程》《UI线程与worker线程》引出,UI线程与worker线程“串行化”在win32上实现是多么没有节操的事情,代码编写麻烦不说,编写过程中容易打断思路,还不易于维护,遇到这种重复性高的代码(即使操作步骤一样),就像眼里的沙,一刻都容忍不得,必须想出一种办法解决这种现状。
由于之前项目中需要临时用C#写个小工具,用来调试和测试,当中也遇到了类似的问题,其中发现,.net本身就提供了解决这类问题的方法,而且很简单,在worker线程执行处先判断当前Form对象的InvokeRequired属性,如果需要等待UI线程执行完,则创建一个delegate给invoke,妈蛋,MS那帮懒人,MFC就是一直不更新,只能自己动手了。
既然.net提供了这么一个方法可以解决这个问题,那win32程序自然也可以有这么一种方法(当然不是使用.net那种),实在没有的话模仿它,照猫话虎也行。好,动手干吧,实验一下又不会怀孕。在整理出需要关注哪些细节问题之前,先明确一下目的,换句话说,先明确一下到时这个实验结果怎么应用到工程当中。
既然有.net这样样板可供参考,那就按照它那样来,到时client代码应该写起来像这样:
嗯,看起来像回事,接下来看怎么实现了。
1.需要有个可调用的东西用来判断当前是否需要invoke,即当前线程是否与UI线程相同,这最好办了;
2.既然只能通过sendmessage实现,那invoke就是对sendmessage的封装,并且把window_obj::received_result这个member function用boost::function打包一下,通过指针传给sendmessage作为参数叫给UI线程调用;既然是sendmessage,就必须得自定义一个消息,然后在window_obj的消息响应里执行这个worker传过来的functor,好吧,就用RegisterWindowMessage这个API生成Message ID吧;
3.既然要省去添加消息映射宏,那就得消息处理函数认识这个消息id,有两个方案可选消息钩子、修改这个窗口的窗口过程函数入口SetWindowLong,后一种简单一些;把原先的窗口过程函数用变量old_proc_保存起来,以便这个消息id之外的其他消息能正常响应,窗口过程函数又是一个全局函数或静态函数,那old_proc_变量也必须是全局或静态的,且这是在多线程环境,那必须对old_proc_变量值的修改进行加锁;
4.考虑到对窗口调用SetWindowLong时要对old_proc_值的更改进行安全保护而加锁,为了把这种加锁解锁的开销降到最低,又要满足任何窗口类型都能使用这个类,把这个类设计成模板类是个不错的选择。
根据以上4点,基本满足了使用需求,先写出代码:
然后像这样使用
若要支持更多的参数个数,照猫画虎写多几个宏定义即可,不过通常一个函数要是函数参数过多,我也会抓狂。。。
由于之前项目中需要临时用C#写个小工具,用来调试和测试,当中也遇到了类似的问题,其中发现,.net本身就提供了解决这类问题的方法,而且很简单,在worker线程执行处先判断当前Form对象的InvokeRequired属性,如果需要等待UI线程执行完,则创建一个delegate给invoke,妈蛋,MS那帮懒人,MFC就是一直不更新,只能自己动手了。
既然.net提供了这么一个方法可以解决这个问题,那win32程序自然也可以有这么一种方法(当然不是使用.net那种),实在没有的话模仿它,照猫话虎也行。好,动手干吧,实验一下又不会怀孕。在整理出需要关注哪些细节问题之前,先明确一下目的,换句话说,先明确一下到时这个实验结果怎么应用到工程当中。
既然有.net这样样板可供参考,那就按照它那样来,到时client代码应该写起来像这样:
void window_obj::create_and_run_thread(){ boost::thread t1(boost::bind(&window_obj::thread_func1, this)); boost::thread t2(boost::bind(&window_obj::thread_func2, this)); }
void window_obj::thread_func1(){ for (;;){ if (need_exit) break; // do something to ask for result received_result1(result); } } void window_obj::thread_func2(){ for (;;){ if (need_exit) break; // do something to ask for result received_result2(result1, result2); } } void window_obj::received_result1(result_type result1){ // 最好能用把这个条件分支用宏定义代替,要简单直观 if (required){ invoke(window_obj::received_result1, this, result1); return; } // do something with result 1 in UI thread } void window_obj::received_result2(result_type result1, other_type result2){ // 然后在把这个条件分支用宏定义代替,要简单直观 if (required){ invoke(window_obj::received_result2, this, result1, result2); return; } // do something with result1 and result2 in UI thread }
嗯,看起来像回事,接下来看怎么实现了。
1.需要有个可调用的东西用来判断当前是否需要invoke,即当前线程是否与UI线程相同,这最好办了;
2.既然只能通过sendmessage实现,那invoke就是对sendmessage的封装,并且把window_obj::received_result这个member function用boost::function打包一下,通过指针传给sendmessage作为参数叫给UI线程调用;既然是sendmessage,就必须得自定义一个消息,然后在window_obj的消息响应里执行这个worker传过来的functor,好吧,就用RegisterWindowMessage这个API生成Message ID吧;
3.既然要省去添加消息映射宏,那就得消息处理函数认识这个消息id,有两个方案可选消息钩子、修改这个窗口的窗口过程函数入口SetWindowLong,后一种简单一些;把原先的窗口过程函数用变量old_proc_保存起来,以便这个消息id之外的其他消息能正常响应,窗口过程函数又是一个全局函数或静态函数,那old_proc_变量也必须是全局或静态的,且这是在多线程环境,那必须对old_proc_变量值的修改进行加锁;
4.考虑到对窗口调用SetWindowLong时要对old_proc_值的更改进行安全保护而加锁,为了把这种加锁解锁的开销降到最低,又要满足任何窗口类型都能使用这个类,把这个类设计成模板类是个不错的选择。
根据以上4点,基本满足了使用需求,先写出代码:
#include <map> #include <set> #include <boost/function.hpp> #include <boost/thread.hpp> namespace invoke { /* ** to check if the calling thread is different from UI thread */ static bool required(){ return ::GetCurrentThreadId() != AfxGetApp()->m_nThreadID; } ////////////////////////////////////////////////////////////////////////// typedef boost::function<void (void)> operate_func; ////////////////////////////////////////////////////////////////////////// template<class T> class dispatch { static boost::mutex mtx_; static WNDPROC old_proc_; public: static UINT msg_id_; protected: dispatch(){} static UINT wnd_proc(HWND hWnd, UINT msg, WPARAM w, LPARAM l) { if (msg == msg_id_){ // call the message function (*(invoke::operate_func*)l)(); return 1; } return ::CallWindowProc(old_proc_, hWnd, msg, w, l); } public: static void request_invoke(HWND hWnd) { if (!old_proc_){ // maybe other thread is update the old_proc_ value boost::unique_lock<boost::mutex> guard(mtx_); if (!old_proc_) // i am sure that the old_proc_ update operation won't conflict old_proc_ = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)&wnd_proc); } } }; template<class T> boost::mutex dispatch<T>::mtx_; template<class T> WNDPROC dispatch<T>::old_proc_ = NULL; template<class T> UINT dispatch<T>::msg_id_ = ::RegisterWindowMessage(_T("INVOKE_MSG_DEFINE")); } 然后使用的时候像这样: void window_obj::received_result1(result_type result1){ // 最好能用把这个条件分支用宏定义代替,要简单直观 if (this == NULL || !::IsWindow(GetSafeHwnd())) return; if (invoke::required()){ invoke::dispatch<cls>::request_invoke(GetSafeHwnd()); UINT msg = invoke::dispatch<cls>::msg_id_; invoke::operate_func f = boost::bind(&cls::func, this, result1); ::SendMessage(GetSafeHwnd(), msg, (WPARAM)0, (LPARAM)&f); return; } // do something with result 1 in UI thread } 如果函数中有多个参数需要传递给UI线程,那也只需把要传递的参数在boost::bind(&cls::func, this, result1);末尾添加进去打包成boost::function就行了,那剩下的就是把它写成宏定义,使用的时候方便。 #define NEED_INVOKE_BEGIN(cls, func) \ if (this == NULL || !::IsWindow(GetSafeHwnd())) \ return; \ if (invoke::required()){ \ invoke::dispatch<cls>::request_invoke(GetSafeHwnd()); \ UINT msg = invoke::dispatch<cls>::msg_id_; \ invoke::operate_func f = boost::bind(&cls::func, this #define NEED_INVOKE_END \ ); \ ::SendMessage(GetSafeHwnd(), msg, (WPARAM)0, (LPARAM)&f); \ return; \ } #define NEED_INVOKE0(cls, func) \ NEED_INVOKE_BEGIN(cls, func) \ NEED_INVOKE_END #define NEED_INVOKE1(cls, func, a1) \ NEED_INVOKE_BEGIN(cls, func) \ , boost::ref(a1) \ NEED_INVOKE_END #define NEED_INVOKE2(cls, func, a1, a2) \ NEED_INVOKE_BEGIN(cls, func) \ , boost::ref(a1) \ , boost::ref(a2) \ NEED_INVOKE_END #define NEED_INVOKE3(cls, func, a1, a2, a3) \ NEED_INVOKE_BEGIN(cls, func) \ , boost::ref(a1) \ , boost::ref(a2) \ , boost::ref(a3) \ NEED_INVOKE_END
然后像这样使用
void window_obj::received_result1(result_type result1){ NEED_INVOKE2(window_obj, received_result1, result1); // do something with result 1 in UI thread }
若要支持更多的参数个数,照猫画虎写多几个宏定义即可,不过通常一个函数要是函数参数过多,我也会抓狂。。。
相关文章推荐
- 模板应用--UI线程与worker线程同步 模仿c# invoke
- c# InvokeRequired 解决跨线程访问UI控件的问题
- C#WinForm的线程及Invoke应用(转)
- c#多线程(UI线程,控件显示更新) Invoke和BeginInvoke 区别
- C#线程系列讲座(1):BeginInvoke和EndInvoke方法
- C# BeginInvoke和EndInvoke方法操作线程
- C#线程:BeginInvoke和EndInvoke方法
- 线程高级应用-心得5-java5线程并发库中Lock和Condition实现线程同步通讯
- C#中的线程(二) 线程同步基础
- C# WeifenLuo.WinFormsUI.Docking.dll 应用之问题集 子窗体访问父窗体方法
- C#后台线程和UI的交互
- 千万别在UI线程上调用Control.Invoke和Control.BeginInvoke,因为这些是依然阻塞UI线程的,造成界面的假死
- C# 多线程详解 Part.01(UI 线程、子线程)
- c#中跨线程调用windows窗体控件 .我们在做winform应用的时候,大部分情况下都会碰到使用多线程控制界面上控件信息的问题。然而我们并不能用传统方法来做这个问题,下面我将详细的介绍。
- C#中的线程二(BeginInvoke和Invoke)
- Android的UI组件不是线程安全的,不应该在worker线程操纵UI
- ModernUI教程:第一个ModernUI应用(采用项目模板)
- C#UI线程产生的子线程如何在UI结束时,干净的结束
- C#线程系列讲座(2):Thread类的应用
- C#开发微信门户及应用(27)-公众号模板消息管理