让Duilib多线程编程更容易

原文链接https://i-blog.csdnimg.cn/blog_migrate/3a720997a44f276e805a2c13d90f0042.png

     一、Duilib不能开发多线程程序?    

         记得很久以前就听有人说过Duilib的多线程支持性不好,原因是Duilib里面的控件是用数组管理的全局变量,不能进行多线程访问,加锁很麻烦。其实这个说法是非常不合理的,以至于在开发多线程程序时直接将Duilib拒之门外。当然使用Duilib里面开发多线程是木有任何问题的,不要单纯地认为:其他的界面库就能使用多个线程同时操作一个控件,Duilib就不行。事实证明,这点MFC做不到,WinForm也做不到,连微软自己都搞不定的东西,不能算Duilib的缺陷。

         二、UI线程与工作线程

      凡是开发过Windows界面的人应该都知道,先要做出稳定并且没有闪烁的界面,所有的绘制代码,必须是顺序执行的,画完一个再画一个,而且绘制代码都要写在一个位置,WM_PAINT里面(当然你写在外面也没问题,但还要处理擦除,一般不这么干)。所以,所有的绘制都是异步执行的,想绘制什么,就得先保存下来,使用Invalidate()去异步触发WM_PAINT,待WM_PAINT消息处理时,再去绘制。就这一点就决定了,绘制UI多线程是行不通的,所以所有的绘制操作都是消息循环所在的主线程完成的。既然多线程不能用于绘制,那么多个线程同时访问同一个控件的属性怎么样?(比如,有一个按钮A,一个线程执行A->SetWith(600),另一个线程执行A->SetWith(800);)这同样是一个坏主意,控件的width属性是由一个int值来标志的,SetWith这一类的操作,既没有使用原子锁,又没有任何互斥锁,同时访问是有可能出问题的。也许你曾经这样弄过,并没有发现问题,那是因为两个线程“碰上”的几率不大,是你没有碰上而已,但不要存在这种侥幸心理,几率不大不代表没有,一旦出现势必会出错。要想控件属性支持多线程访问,要么每一个操作都使用原子操作符,要么把每个方法都加上互斥锁。然而这样弄是根本划不来的,效率太低了。那么当工作线程需要去修改控件属性怎么办呢?同样是采用异步的方法,在线程里面post自定义消息,然后再窗口函数里面处理其他线程发过来的消息,去回调应该处理的操作。为什么称之为异步?就是因为我们利用了Windows的“系统消息队列”来充当了我们的异步事件处理队列,无论是“并行“事件还是”串行“事件,经过消息队列编排之后都会串行处理,化多线程为单线程。


 比如,下载完成的通知,是下载线程调用的,这时我们想去修改控件属性时,我们要post一个自定义消息

  1. void CMainFrame::OnFinished(int percent,TASK*)  
  2. {  
  3.     PostMessage(WM_UPDATEAD,0,0);  
  4. }  


   在窗口过程处理函数里面去操作控件
  1. LRESULT CMainFrame::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)  
  2. {  
  3.     if (uMsg == WM_UPDATEAD)  
  4.     {  
  5.         CDuiString htmlPath = L"file:///";  
  6.         CDuiString currentPath = m_PaintManager.GetCurrentPath();  
  7.         htmlPath.Append(currentPath);  
  8.         htmlPath.Append(L"/html/Advertisement/ad.html");  
  9.         m_pWkeWeb->LoadUrl(L"/html/Advertisement/ad.html");  
  10.     }  

     但是这样写是一件十分令人头疼的事情,要为每一个函数都弄一个自定义消息,你不嫌烦编译器都嫌烦了。


三、Skilla为Duilib编写的应用程序框架skillcore,可以完美胜任多线程

   


             Duilib虽然在控件绘制上,花的心思不少。但是在应用程序管理方面却还在“刀耕火种”的时代,还在使用最原始的WinMain。这样做虽然对使用者透明,但是对于多线程程序,不多点花点心思还真是处理不好。就单纯一个析构顺序颠倒的问题,就能导致程序崩溃。所以Skilla仿照Juce的应用程序框架为Duilib写了一套,来解决这个问题(当然没有Juce里面那么复杂了),下面我们来看如何使用。

  1. class TestApp : public DuiApplicationBase  
  2. {  
  3. public:  
  4.     void OnInit();  
  5.     void RequestQuit();  
  6.     void OnQuit();  
  7.     ScopedPtr<CMainFrame> m_pFrame;  
  8. };  
  9.   
  10. START_DUI_APPLICATION(TestApp)  
  11.   
  12.   
  13. void TestApp::OnInit()  
  14. {  
  15.     wkeInit();  
  16.     CPaintManagerUI::SetInstance(*this);  
  17.     CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());  
  18.     m_pFrame = new CMainFrame;  
  19.     if (m_pFrame == NULL) return ;  
  20.     m_pFrame->Create(NULL, _T("DuilibDemo"), UI_WNDSTYLE_FRAME, WS_EX_STATICEDGE | WS_EX_APPWINDOW, 0, 0, 800, 600);  
  21.     m_pFrame->CenterWindow();  
  22.     ::ShowWindow(*m_pFrame, SW_SHOW);  
  23. }  
  24.   
  25. void TestApp::RequestQuit()  
  26. {  
  27.     wkeShutdown();  
  28.     Quit();  
  29. }  
  30.   
  31.  void TestApp::OnQuit()  
  32. {  
  33.   
  34. }  

         怎么样,是不是很简单?DuiApplicationBase之所以把消息循环封装在里面就是为了处理多线程的事件用的。当然就这么一个玩意并不能保证Duilib能在多线程编程中游刃有余。skillcore同样还为线程做了封装,对消息循环也做了封装,同时为了简化多线程编程时复杂的内存管理,对内存,指针同样做了封装,源码主要借鉴于boost库和Juce库,同时还有文件,输入输出流,原子锁,互斥锁等一堆辅助的东西。

        刚才说道异步调用函数,弄自定义消息灰常麻烦。为了解决这个问题,skillcore提供了一个MsgPump的类,用这个对象可以直接在其他线程中使用主线程调用函数。下面我们看一下具体是怎样使用的。下面的代码是一个窗口关闭前的一个“收敛”动作的线程动画,类似于酷狗关闭时的效果。

  1. class WindowDestroyThread : public Thread  
  2. {  
  3. public:  
  4.     WindowDestroyThread()  
  5.         :Thread(L"DestroyThread")  
  6.     {  
  7.   
  8.     }  
  9.   
  10.     ~WindowDestroyThread()  
  11.     {  
  12.         Stop(4000);  
  13.     }  
  14.   
  15.   
  16.     void run()  
  17.     {  
  18.         while (!IsShouldExit())  
  19.         {  
  20.             if (MsgPump::GetInstance()->IsShouldStop()) break;  
  21.             B_Function<int(WindowDestroyThread*)> f = B_Bind(WindowDestroyAnimation,_1);  
  22.             if (!MsgPump::GetInstance()->CallFun(f,this))  
  23.             {  
  24.                 static_cast<TestApp*>(DuiApplicationBase::GetInstance())->RequestQuit();  
  25.             }  
  26.   
  27.             Sleep(10);  
  28.         }  
  29.     }  
  30.     LEAKED_THE_CLASS(WindowDestroyThread)  
  31. };  
  32.   
  33. int WindowDestroyAnimation(WindowDestroyThread* t)  
  34. {  
  35.     TestApp * app = static_cast<TestApp*>(DuiApplicationBase::GetInstance());  
  36.     CMainFrame* p = app->m_pFrame;  
  37.     RECT rect;  
  38.     ::GetWindowRect(*p,&rect);  
  39.     CDuiRect r(rect);  
  40.     if (r.GetHeight()>100)  
  41.     {  
  42.         MoveWindow(*p,rect.left,rect.top+15,r.GetWidth(),r.GetHeight()-30,false);  
  43.         return 1;  
  44.     }  
  45.     else  
  46.     return 0;  
  47. }  
  48.   
  49.   
  50. void CMainFrame::Notify(TNotifyUI& msg)  
  51. {  
  52.     if (_tcsicmp(msg.sType,_T("windowinit"))==0)  
  53.     {  
  54.         MoveWindow(*this,0,0,900,100,false);  
  55.         CenterWindow();  
  56.         initThread = new WindowInitThread;  
  57.         initThread->Start();  
  58.   
  59.         CWkeBrowserUI* pBrowser = (CWkeBrowserUI*)m_PaintManager.FindControl(L"wke");  
  60.         pBrowser->LoadUrl(L"www.baidu.com");  
  61.     }else if (_tcsicmp(msg.sType,_T("click")) == 0)  
  62.     {  
  63.         if (_tcsicmp(msg.pSender->GetName(),_T("btn_close")) == 0)  
  64.         {  
  65.             destroyThread = new WindowDestroyThread();  
  66.             destroyThread->Start();  
  67.               
  68.         }  
  69.   
  70.     }  
  71. }  

         使用时,先把需要调用的函数,绑定成B_Funtion(boost::function)对象,然后使用MsgPump的单例对象的CallFun函数,去调用即可,第一个参数传递的是绑定好的B_Function对象,后面依次是被调用函数的参数。目前最多支持4个参数,如果需要更多的话,可以自己去扩展,因为每加一个参数都需要写一段重复性代码,Skilla已经写了4个,实在是不愿再写了。

        还有里面封装了一堆智能指针,相信这些应该不用讲大家也应该知道怎么用。有ScopePtr自生自灭类型,SharedPtr无根强引用型,WeakPtr无根弱引用型,SharedObjectPtr有根强引用型,ComSmartPtr调用Com接口用的智能指针。

        Threads里面还封装了多线程中常用到的临界区,自旋锁,事件等加锁机制,使用变得更简单。


        讲了这么多,相信大家都已经手痒了,有一种跃跃欲试的感觉。不要急,Skilla已经把源码贡献出来了,附带一个窗口动画的Demo,链接地址在下面。炫酷的线程动画你也可以轻松实现。

 

   skillcore和测试Demo下载链接(里面的CWkeBrowser控件也是用线程来刷新的哦,比以前使用定时器要流畅)

 

      由于时间关系,代码构建的比较仓促,可能库本身还存在bug以及不足,如果发现请联系Skilla(QQ:848861075),谢谢!



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值