由之前的一篇博文 《UI线程与worker线程》《UI线程与worker线程》引出,UI线程与worker线程“串行化”在win32上实现是多么没有节操的事情,代码编写麻烦不说,编写过程中容易打断思路,还不易于维护,遇到这种重复性高的代码(即使操作步骤一样),就像眼里的沙,一刻都容忍不得,必须想出一种办法解决这种现状。

由于之前项目中需要临时用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的更多相关文章

  1. UI线程与worker线程

    也谈谈我对UI线程和worker线程的理解 UI线程又叫界面线程,能够响应操作系统的特定消息,包括界面消息.鼠标键盘消息.自定义消息等,是在普通的worker线程基础上加上消息循环来实现的,在这个消息 ...

  2. 在netty3.x中存在两种线程:boss线程和worker线程。

    在netty 3.x 中存在两种线程:boss线程和worker线程.

  3. Android 实战之UI线程和Worker线程交互

    哈哈,博文取了个比较霸气的名字,大家不都喜欢这样忽悠人吗 呵呵! 好了,现在就是很简单的点击查询,然后这个查询有点花时间,不想见面出现假死现象,所以在另外的线程进行查询. 好了,代码在此: packa ...

  4. UI线程和工作者线程

    本文转载于:http://blog.csdn.net/libaineu2004/article/details/40398405 1.线程分为UI线程和工作者线程,UI线程有窗口,窗口自建了消息队列, ...

  5. HTML5_06之拖放API、Worker线程、Storage存储

    1.拖放API中源对象与目标对象事件间的数据传递: ①创建全局变量--污染全局对象:  var 全局变量=null;  src.ondragstart=function(){   全局变量=数据值;  ...

  6. 为什么说android UI操作不是线程安全的

    转载于:http://blog.csdn.net/lvxiangan/article/details/17218409#t2 UI线程及Android的单线程模型原则 使用Worker线程 Commu ...

  7. C# 多线程详解 Part.02(UI 线程和子线程的互动、ProgressBar 的异步调用)

           我们先来看一段运行时会抛出 InvalidOperationException 异常的代码段: private void btnThreadA_Click(object sender, ...

  8. 由UI刷新谈到线程安全和Android单线程模型

    1.为什么说invalidate()不能直接在线程中调用? Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在非UI主线程中调用,因为他是违背了单线程模型:A ...

  9. 【转】 Pro Android学习笔记(九十):了解Handler(4):Worker线程

    目录(?)[-] worker线程小例子 小例子代码worker线程通过handler实现与主线程的通信 小例子代码继承Handler代码 小例子代码子线程的Runnable 文章转载只能用于非商业性 ...

随机推荐

  1. Hbase深入学习(二) 安装hbase

    Hbase深入学习(二) 安装hbase This guidedescribes setup of a standalone hbase instance that uses the local fi ...

  2. GRUB启动管理器

    Linux学习笔记之 5 Linux GRUB启动管理器 1.GRUB简介 1.1grub与启动引导器     启动引导器是计算机启动过程中运行的第一个真正的软件,通常计算机启动时在通过BIOS自检后 ...

  3. 【LeetCode练习题】Minimum Depth of Binary Tree

    Minimum Depth of Binary Tree Given a binary tree, find its minimum depth. The minimum depth is the n ...

  4. FWA winner | Car Visualizer WebGL

    FWA winner | Car Visualizer WebGL Car Visualizer made in WebGL using ThreeJS. It requires a modern b ...

  5. C语言使用正则表达式

    http://blog.chinaunix.net/uid-479984-id-2114941.html C语言使用正则表达式 据说一个好的程序员是会使用DB和Regular Expression的程 ...

  6. Android学习总结——Service组件

    从Service的启动方式上,可以将Service分为Started Service和Bound Service.在使用Service时,要想系统能够找到此自定义Service,无论哪种类型,都需要在 ...

  7. 使用nodeitk进行角点检測

    前言 东莞,晴,33至27度.今天天气真好,学生陆续离开学校.忙完学生答辩事情,最终能够更新一下nodeitk.本文继续介绍node的特征识别相关内容,你会看到,採用nodeitk实现角点检測是一件十 ...

  8. js 的try catch

    语法: try { //在此运行代码 } catch(err) { //在此处理错误 } 注意:try...catch 使用小写字母.大写字母会出错. <script language=&quo ...

  9. C# 静态类与非静态类、静态成员的区别分析

    静态类静态类与非静态类的重要区别在于静态类不能实例化,也就是说,不能使用 new 关键字创建静态类类型的变量.在声明一个类时使用static关键字,具有两个方面的意义:首先,它防止程序员写代码来实例化 ...

  10. Eclipse MyEclipse 复制项目 复制现有项目 复制功能相似项目

    如果现在已经存在一个Java Web项目 ProjectA,现在想做另外一个项目,里面绝大部分功能和结构都可以复用,如果想通过复制的方法来,那么可以这么做: 1.到资源管理器中,将ProjectA文件 ...