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

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

二、UI线程与工作线程

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

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

void CMainFrame::OnFinished(int percent,TASK*)
{
PostMessage(WM_UPDATEAD,0,0);
}

在窗体过程处理函数里面去操作控件

LRESULT CMainFrame::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_UPDATEAD)
{
CDuiString htmlPath = L"file:///";
CDuiString currentPath = m_PaintManager.GetCurrentPath();
htmlPath.Append(currentPath);
htmlPath.Append(L"/html/Advertisement/ad.html");
m_pWkeWeb->LoadUrl(L"/html/Advertisement/ad.html");
}

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

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

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

	class TestApp : public DuiApplicationBase
{
public:
void OnInit();
void RequestQuit();
void OnQuit();
ScopedPtr<CMainFrame> m_pFrame;
}; START_DUI_APPLICATION(TestApp) void TestApp::OnInit()
{
wkeInit();
CPaintManagerUI::SetInstance(*this);
CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());
m_pFrame = new CMainFrame;
if (m_pFrame == NULL) return ;
m_pFrame->Create(NULL, _T("DuilibDemo"), UI_WNDSTYLE_FRAME, WS_EX_STATICEDGE | WS_EX_APPWINDOW, 0, 0, 800, 600);
m_pFrame->CenterWindow();
::ShowWindow(*m_pFrame, SW_SHOW);
} void TestApp::RequestQuit()
{
wkeShutdown();
Quit();
} void TestApp::OnQuit()
{ }

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

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

class WindowDestroyThread : public Thread
{
public:
WindowDestroyThread()
:Thread(L"DestroyThread")
{ } ~WindowDestroyThread()
{
Stop(4000);
} void run()
{
while (!IsShouldExit())
{
if (MsgPump::GetInstance()->IsShouldStop()) break;
B_Function<int(WindowDestroyThread*)> f = B_Bind(WindowDestroyAnimation,_1);
if (!MsgPump::GetInstance()->CallFun(f,this))
{
static_cast<TestApp*>(DuiApplicationBase::GetInstance())->RequestQuit();
} Sleep(10);
}
}
LEAKED_THE_CLASS(WindowDestroyThread)
}; int WindowDestroyAnimation(WindowDestroyThread* t)
{
TestApp * app = static_cast<TestApp*>(DuiApplicationBase::GetInstance());
CMainFrame* p = app->m_pFrame;
RECT rect;
::GetWindowRect(*p,&rect);
CDuiRect r(rect);
if (r.GetHeight()>100)
{
MoveWindow(*p,rect.left,rect.top+15,r.GetWidth(),r.GetHeight()-30,false);
return 1;
}
else
return 0;
} void CMainFrame::Notify(TNotifyUI& msg)
{
if (_tcsicmp(msg.sType,_T("windowinit"))==0)
{
MoveWindow(*this,0,0,900,100,false);
CenterWindow();
initThread = new WindowInitThread;
initThread->Start(); CWkeBrowserUI* pBrowser = (CWkeBrowserUI*)m_PaintManager.FindControl(L"wke");
pBrowser->LoadUrl(L"www.baidu.com");
}else if (_tcsicmp(msg.sType,_T("click")) == 0)
{
if (_tcsicmp(msg.pSender->GetName(),_T("btn_close")) == 0)
{
destroyThread = new WindowDestroyThread();
destroyThread->Start(); } }
}

使用时,先把须要调用的函数,绑定成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),谢谢!

让Duilib多线程编程更easy的更多相关文章

  1. .NET面试题解析(07)-多线程编程与线程同步

      系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实是很多的,比如多线程编程.线程上下文.异步编程.线程同步构造.GUI的跨线程访问等等, ...

  2. Linux多线程编程(不限Linux)【转】

    ——本文一个例子展开,介绍Linux下面线程的操作.多线程的同步和互斥. 前言 线程?为什么有了进程还需要线程呢,他们有什么区别?使用线程有什么优势呢?还有多线程编程的一些细节问题,如线程之间怎样同步 ...

  3. .NET面试题解析(07)-多线程编程与线程同步 (转)

    http://www.cnblogs.com/anding/p/5301754.html 系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实 ...

  4. Linux多线程编程(不限Linux)

    前言 线程?为什么有了进程还需要线程呢,他们有什么区别?使用线程有什么优势呢?还有多线程编程的一些细节问题,如线程之间怎样同步.互斥,这些东西将在本文中介绍.我在某QQ群里见到这样一道面试题: 是否熟 ...

  5. Linux多线程编程(不限Linux)转

    ——本文一个例子展开,介绍Linux下面线程的操作.多线程的同步和互斥. 前言 线程?为什么有了进程还需要线程呢,他们有什么区别?使用线程有什么优势呢?还有多线程编程的一些细节问题,如线程之间怎样同步 ...

  6. Linux多线程编程小结

     Linux多线程编程小结 前一段时间由于开题的事情一直耽搁了我搞Linux的进度,搞的我之前学的东西都遗忘了,非常烦躁的说,如今抽个时间把之前所学的做个小节.文章内容主要总结于<Linux程序 ...

  7. Linux多线程编程初探

    Linux线程介绍 进程与线程 典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情.有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程 ...

  8. [Java123] JDBC and Multi-Threading 多线程编程学习笔记

    项目实际需求:DB交互使用多线程实现 多线程编程基础:1.5  :( (假设总分10) 计划一个半月从头学习梳理Java多线程编程基础以及Oracle数据库交互相关的多线程实现 学习如何通过代码去验证 ...

  9. iOS多线程编程(四)------ GCD(Grand Central Dispatch)

    一.简单介绍 是基于C语言开发的一套多线程开发机制.也是眼下苹果官方推荐的多线程开发方法.用起来也最简单.仅仅是它基于C语言开发,并不像NSOperation是面向对象的开发.而是全然面向过程的.假设 ...

随机推荐

  1. DOM相关知识总结

    DOM相关: 1.获取DOM元素 document.getElementById document.getElementsByName document.getElementsByTagName do ...

  2. WCF学习笔记——对象序列化

    当试图通过Web服务.WCF这样的远程处理技术将一个对象复制到远端时,具有对类型序列化的能力很关键. 一 序列化基础 序列化描述了持久化或传输一个对象的状态到流的过程(.NET将对象序列化到流,流是字 ...

  3. HttpClient请求发送的几种用法二:

    public class HttpClientHelper    {        private static readonly HttpClientHelper _instance = new H ...

  4. 嵌入式Linux学习笔记 NAND Flash控制器

    一.NAND Flash介绍和NAND Flash控制器的使用 NAND Flash在嵌入式系统中的作用,相当于PC上的硬盘 常见的Flash有NOR Flash和NAND Flash,NOR Fla ...

  5. java回调函数这样说,应该明确了吧!

    有哥们问我回调怎么用,回调怎么理解? 怎么说好呢,仅仅可意会不可言传呐,非也,回调在实际开发中使用频率事实上是非常高的,恰好我小时候也被回调函数欺负过,居然问了,那么肯定要好好分享一下我的一些经验. ...

  6. NSString常见用法

    1.创建常量字符串 NSString *str = @"Hello World!"; 2.创建空字符串,给予赋值 NSString *str = [[NSString alloc] ...

  7. Cocos2dx 小技巧(十六)再谈visit(getDescription)

    之前两篇都是介绍与Value相关的,这篇我继续这个话题吧,正好凑个"Value三板斧系列...".在非常久非常久曾经.我用写过一篇博客,关于怎样查看CCArray与CCDictio ...

  8. js私有变量

    js私有变量 一.总结 1.在js函数中定义 this.name='张三'; (函数的属性)外部是可以访问的,但是 var name='张三'; (函数的私有变量),这样定义的话外部没有办法访问 2. ...

  9. 随手可得的Application对象

    在Android的开发其中,Application和Context对象应该是我们接触最多的对象了,特别是Context对象. 当我们在某个Activity或者Service其中时,因为它们本身就是Co ...

  10. 线程堆栈大小 pthread_attr_setstacksize 的使用

    pthread_create 创建线程时,若不指定分配堆栈大小,系统会分配默认值,查看默认值方法如下: # ulimit -s8192# 上述表示为8M:单位为KB. 也可以通过# ulimit -a ...