由之前的一篇博文 《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. Structs2中Action返回json到前台方法

    1.传统方式JSON输出 这一点跟传统的Servlet的处理方式基本上一模一样,代码如下 01 public void doAction() throws IOException{ 02        ...

  2. bzoj1637 [Usaco2007 Mar]Balanced Lineup

    Description Farmer John 决定给他的奶牛们照一张合影,他让 N (1 ≤ N ≤ 50,000) 头奶牛站成一条直线,每头牛都有它的坐标(范围: 0..1,000,000,000 ...

  3. LeeCode-Spiral Matrix II

    Given an integer n, generate a square matrix filled with elements from 1 to n2 in spiral order. For ...

  4. UILabel Text 加下划线

    .h文件 #import <Foundation/Foundation.h> @interface CustomLabel : UILabel { BOOL _isEnabled; } @ ...

  5. 检查ORACLE的警告文件的脚本

    检查两天内的须要重视的信息: vi   alter_error.sh echo "Check Alter Error:" cat $TRACE/alert_$ORACLE_SID. ...

  6. Linux Tomcat7.0安装配置实践总结

    一,安装JDk 先下载jdk,链接http://www.oracle.com/technetwork/java/javase/downloads/index.html,选择相对应平台的JDK.由于笔者 ...

  7. oracle中的rowid--伪列-删除表中的重复内容-实用

    1.rowid是一个伪列,是用来确保表中行的唯一性,它并不能指示出行的物理位置,但可以用来定位行. 2.rowid是存储在索引中的一组既定的值(当行确定后).我们可以像表中普通的列一样将它选出来. 3 ...

  8. RFC端口号定义

    RFC关于计算机端口号定义 http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers. ...

  9. differ比较两个字符串的差异

    "abcde","abdefk"  ---->-c,+f,+k "aba","aababb"    -----&g ...

  10. C++中把string转成char

    c_str函数的返回值是const char*的,不能直接赋值给char*, c++语言提供了两种字符串实现,其中较原始的一种只是字符串的c语言实现. 与C语言的其他部分一样,它在c+的所有实现中可用 ...