基于MFC的界面程序中,如果存在多线程,一般情况下只有主线程(界面线程)可以调用Windows窗口相关的函数,否则如果在其他线程中调用了界面函数,很可能会造成异常。为此DuiVision界面库提供了一个任务队列和相应的调度机制,可以将各种任务对象放到任务队列中按顺序执行,通过任务队列,可以做到其他线程和界面线程之间的中转调用,方法是创建任务对象时候指定是需要界面线程处理的任务,则任务调用过程中会通过跨进程的消息将此任务通知界面线程来执行。
DuiVision中定义了任务的基类和几种派生类,基类是IBaseTask类(从金山界面库移植的),派生类目前有两种,分别是DuiSystem中定义的CDuiActionTask类(用于执行菜单等动作),另一种是DuiSystem中定义的CDuiNotifyMsgTask类,用于执行桌面右下角的弹出提示窗口,这两个任务类的核心执行代码都在DoAction函数中,如果需要派生其他的任务类,可以参考者两个类的实现方式。
IBaseTask类的定义如下:

//
// 任务基类
//
class CTaskMgr;
class IBaseTask
{
public://// 优先级//enum TPriority{TP_Highest      = 0,TP_Above_Normal = (1<<8),TP_Normal       = (1<<16),TP_Below_Normal = (1<<24),TP_Lowest       = (1<<30),TP_Invalid      = (TP_Lowest + 1),};//// 事件//enum TEvent{TE_Unknown = 0,TE_Canceled,TE_Removed,TE_Completed,};public:IBaseTask(LONG type);virtual ~IBaseTask() {}public://// 任务信息//void SetId(LONG id);LONG GetId() const;// 类型必须大于0void SetType(LONG type);LONG GetType() const;// 优先级void SetPriority(ULONG priority);ULONG GetPriority() const;// 是否UI任务void SetUITask(BOOL bUITask);BOOL IsUITask() const;//// 引用计数//LONG AddRef();LONG Release();//// 任务操作////@Return// 返回TRUE,则继续处理,否则放弃该任务//virtual BOOL TaskProcess(CTaskMgr *pMgr);virtual void TaskNotify(CTaskMgr *pMgr, TEvent event);private:IBaseTask(const IBaseTask&);IBaseTask& operator=(const IBaseTask&);protected:LONG            m_id;       // 任务对象IDLONG            m_type;     // 任务类型ULONG           m_priority; // 优先级BOOL            m_bUITask;  // 是否需要UI线程处理的任务volatile LONG   m_refCnt;   // 引用计数
};

DuiSystem中控件的action任务处理类定义以及添加action任务的函数如下,添加的方法是首先获取到DuiSystem中定义的任务管理器TaskMgr(一个任务管理器相当于一个任务队列),调用任务管理器的添加函数将新建的action任务处理对象添加到任务队列中并执行:

//
// DUI动作任务类
//
class CDuiActionTask : public DuiVision::IBaseTask
{
public:CDuiActionTask(LONG type, UINT uID, UINT uMsg, WPARAM wParam, LPARAM lParam, CString strControlName, CString strAction, CDuiObject* pParent): DuiVision::IBaseTask(type), m_uID(uID), m_uMsg(uMsg), m_wParam(wParam), m_lParam(lParam),m_pParent(pParent), m_strControlName(strControlName), m_strAction(strAction){SetUITask(TRUE);    // 设置为需要转UI线程处理的任务}// 任务处理virtual BOOL TaskProcess(DuiVision::CTaskMgr *pMgr){DoAction();return TRUE;}void DoAction(){if(!m_strAction.IsEmpty()){// 如果设置了action,则解析执行if(m_strAction.Find(_T("dlg:")) == 0)   // 动作:打开一个对话框,有内存泄漏,改为通过DuiSystem创建和管理{if(m_uMsg == MSG_BUTTON_UP) // 鼠标放开事件才处理{CString strXmlFile = m_strAction;strXmlFile.Delete(0, 4);if(!strXmlFile.IsEmpty()){DuiSystem::ShowDuiDialog(strXmlFile, m_pParent);}}}elseif(m_strAction.Find(_T("link:")) == 0)  // 动作:打开一个页面链接{if(m_uMsg == MSG_BUTTON_UP) // 鼠标放开事件才处理{CString strLink = m_strAction;strLink.Delete(0, 5);if(!strLink.IsEmpty()){ShellExecute(NULL, TEXT("open"), strLink, NULL,NULL,SW_NORMAL);}}}elseif(m_strAction.Find(_T("run:")) == 0)   // 动作:执行一个进程{if(m_uMsg == MSG_BUTTON_UP) // 鼠标放开事件才处理{CString strProcess = m_strAction;strProcess.Delete(0, 4);strProcess.MakeLower();if(!strProcess.IsEmpty()){strProcess.MakeLower();BOOL bForceAdmin = FALSE;if(strProcess.Find(_T("admin@")) == 0){bForceAdmin = TRUE;strProcess.Delete(0, 6);}BOOL bWaitProcess = FALSE;if(strProcess.Find(_T("&")) == (strProcess.GetLength()-1)){bWaitProcess = TRUE;strProcess.Delete(strProcess.GetLength()-1, 1);}if(strProcess.Find(_T(".exe")) == -1){strProcess = DuiSystem::Instance()->GetString(strProcess);}if(strProcess.Find(_T("{platpath}")) == 0){strProcess.Delete(0, 10);strProcess = DuiSystem::GetExePath() + strProcess;}CString strCmdLine = _T("");int nPos = strProcess.Find(_T("|"));if(nPos != -1){strCmdLine = strProcess;strCmdLine.Delete(0, nPos+1);strProcess = strProcess.Left(nPos);}DuiSystem::PathCanonicalize(strProcess);    // 路径标准化DuiSystem::ExecuteProcess(strProcess, strCmdLine, bForceAdmin, bWaitProcess);}}}elseif(m_strAction.Find(ACTION_CLOSE_WINDOW) == 0)  // 动作:关闭指定的窗口{if(m_uMsg == MSG_BUTTON_UP) // 鼠标放开事件才处理{CString strWndName = m_strAction;strWndName.Delete(0, 13);if(!strWndName.IsEmpty()){CDlgBase* pDlg = DuiSystem::Instance()->GetDuiDialog(strWndName);if(pDlg != NULL){//pDlg->PostMessage(WM_QUIT, 0, 0);pDlg->DoClose();}}}}elseif(m_strAction.Find(ACTION_SHOW_WINDOW) == 0)   // 动作:显示指定的窗口{if(m_uMsg == MSG_BUTTON_UP) // 鼠标放开事件才处理{CString strWndName = m_strAction;strWndName.Delete(0, 12);if(!strWndName.IsEmpty()){CDlgBase* pDlg = DuiSystem::Instance()->GetDuiDialog(strWndName);if(pDlg != NULL){pDlg->SetForegroundWindow();pDlg->ShowWindow(SW_NORMAL);pDlg->ShowWindow(SW_SHOW);pDlg->BringWindowToTop();}}}}}else{// 找到控件,调用控件的消息处理CControlBase* pControl = DuiSystem::GetControl(m_pParent, m_uID);if(pControl){pControl->CallDuiHandler(m_uMsg, m_wParam, m_lParam);}else{// 如果未找到控件,则通过DuiSystem调用所有注册的事件处理对象进行处理DuiSystem::Instance()->CallDuiHandler(m_uID, m_strControlName, m_uMsg, m_wParam, m_lParam);}}}protected:CDuiObject* m_pParent;          // 父对象UINT        m_uID;              // 控件IDCString     m_strControlName;   // 控件名UINT        m_uMsg;             // 消息WPARAM      m_wParam;           // 参数1LPARAM      m_lParam;           // 参数2CString     m_strAction;        // 动作
};// 添加DUI动作任务
void DuiSystem::AddDuiActionTask(UINT uID, UINT uMsg, WPARAM wParam, LPARAM lParam, CString strControlName, CString strAction, CDuiObject* pParent)
{DuiVision::CTaskMgr* pTaskMgr = DuiSystem::Instance()->GetTaskMgr();if(pTaskMgr){pTaskMgr->AddTask(new CDuiActionTask(1, uID, uMsg, wParam, lParam, strControlName, strAction, pParent));pTaskMgr->StartTask();}
}

定义任务对象需要UI线程处理的方法是在任务类中调用任务基类的SetUITask函数进行设置,如果任务对象设置了转UI线程处理的标识,则任务管理器在执行到任务队列中这个任务时候,会调用DuiSystem::RunUITask将任务对象转到UI线程处理,任务队列的任务循环函数如下:

unsigned CTaskMgr::Run()
{while(true){//// 1.等待获得任务//::WaitForSingleObject(m_taskEvent, INFINITE);// 退出if(IsExited()) break;IBaseTask *pTask;while(!IsExited() && ((pTask = GetTask()) != NULL)){//// 2.处理任务//if(pTask->IsUITask()){// 需要UI线程处理的任务,转UI线程DuiSystem::RunUITask(pTask, this);}else{// 直接执行if(pTask->TaskProcess(this)){pTask->TaskNotify(this, IBaseTask::TE_Completed);}else{pTask->TaskNotify(this, IBaseTask::TE_Canceled);}}pTask->Release();   // 执行完,减少任务对象的引用计数,如果到0会自动删除}       }return 0;
}

DuiSystem::RunUITask的代码如下:

// 执行UI线程任务(将一个任务通过给主窗口发消息,在主窗口消息中再执行的方式实现任务线程到主界面线程的任务中转)
int DuiSystem::RunUITask(DuiVision::IBaseTask* pTask, const DuiVision::CTaskMgr* pTaskMgr)
{CDlgBase* pDlg = DuiSystem::Instance()->GetDuiDialog(0);if(pDlg == NULL){return FALSE;}return pDlg->SendMessage(WM_UI_TASK, (WPARAM)pTask, (LPARAM)pTaskMgr);
}

可以看到这个函数中首先获取了当前所有对话框中第一个对话框的指针,然后调用对话框的SendMessage函数发送windows窗口消息给这个对话框,消息ID是WM_UI_TASK,这是一个自定义消息,消息的接收处理函数是CDlgBase的OnMessageUITask函数,经过SendMessage发送,对话框接收之后就已经是在UI线程中了,CDlgBase::OnMessageUITask函数实现如下:

// 转UI线程处理的任务
LRESULT CDlgBase::OnMessageUITask(WPARAM wParam, LPARAM lParam)
{DuiVision::IBaseTask* pTask = (DuiVision::IBaseTask*)wParam;DuiVision::CTaskMgr* pTaskMgr = (DuiVision::CTaskMgr*)lParam;BOOL bRet = FALSE;if((pTask != NULL) && (pTaskMgr != NULL)){bRet = pTask->TaskProcess(pTaskMgr);if(bRet){pTask->TaskNotify(pTaskMgr, DuiVision::IBaseTask::TE_Completed);}else{pTask->TaskNotify(pTaskMgr, DuiVision::IBaseTask::TE_Canceled);}}return bRet;
}

这个函数中会根据传入的参数(任务对象指针和任务管理器对象指针),执行对应任务对象的处理函数,从而完成线程的切换。

DuiSystem中封装的两个任务类和处理函数都是使用的DuiSystem中定义的任务管理器对象,也就是用的都是一个任务队列,某些情况下可能不希望用界面库提供的这个任务队列,或者一个任务队列不够,需要再增加几个任务队列,可以参考DuiSystem默认的这个任务队列的定义方法,自己在定义其他的任务管理器对象。一个任务管理器中的任务队列可以看做是一个线程,如果任务队列中某个任务长时间不结束就会导致这个任务队列阻塞,因此如果需要两个任务并行执行,是可以考虑自己再建立新的任务队列,另外对于通信消息等其他的线程消息,也是可以考虑再新建一个独立的任务队列来处理,总之是否需要建立新的任务队列需要根据实际情况来决定。


DuiVision开源代码下载地址(github):https://github.com/blueantst/DuiVision
蓝蚂蚁工作室主页:http://www.blueantstudio.net
DuiVision QQ群:325880743
微信公众号:blueantstudio 或搜索 蓝蚂蚁工作室

DuiVision开发教程(12)-任务类和任务队列相关推荐

  1. DuiVision开发教程(2)-如何写一个简单的界面程序

    基于DuiVision界面库开发的界面程序主要包括如下几部分内容: 1.资源定义,包括图片资源.各个窗口界面的xml定义文件 2.事件处理类代码,用于处理界面响应消息 3.其他业务逻辑代码 下面举例说 ...

  2. MM32F3273G8P火龙果开发板MindSDK开发教程12 -获取msa311加速器的敲击事件

    MM32F3273G8P火龙果开发板MindSDK开发教程12 -获取msa311加速器的敲击事件 1.功能描述 msa311可以识别单击.双击事件,类似手机上的点击返回,双击截屏功能. 单击,双击都 ...

  3. python博客开发教程_Django 博客开发教程 12 - 评论

    创建评论应用 相对来说,评论其实是另外一个比较独立的功能.Django 提倡,如果功能相对比较独立的话,最好是创建一个应用,把相应的功能代码写到这个应用里.我们的第一个应用叫 blog,它里面放了展示 ...

  4. 【开发教程12】AI语音人脸识别(会议记录仪/人脸打卡机)-语音采集

    CC3200AI实验教程 --疯壳·开发板系列 语音采集 音频采集板卡主要运用的是TI官方的方案TLV320AIC3254音频编解码器+TPA2012D2RTJ功率放大器,如图1.0.1为语音采集主板 ...

  5. DuiVision开发教程(19)-菜单

    DuiVision菜单类是CDuiMenu.有两种显示的位置,一种是在窗体顶部某个button点击后能够下拉一个菜单,还有一种是托盘图标的右键菜单. 窗体中的菜单定义方式是xml文件里设置某个butt ...

  6. PalmOS开发教程-12

    第十二章 专业编程技巧      随着程序变得越来越大,我们会发现很多问题.这些问题或许在编制几千行代码时不会出现,但是当编到上万行或更多时并且程序由不同的程序员来编写,在运行时问题就出现了. 这就要 ...

  7. WinForm框架开发教程 - 窗体基类的用户身份信息的缓存和提取

    在WinForm开发中,有时候为了方便,需要把窗体的一些常规性的数据和操作函数进行封装,通过自定义基类窗体的方式,可以实现这些封装管理,让我们的框架统一化.简单化的处理一些常规性的操作,如这里介绍的用 ...

  8. DuiVision开发教程(3)-XML资源文件定义介绍

    全局资源定义-resource.xml 基于DuiVision界面库的程序,需要有一个默认的资源定义XML文件,此文件默认的位置是exe文件所在路径下的xml\resource.xml文件,如果使用了 ...

  9. DuiVision开发教程(17)-对话框

    DuiVision的对话框类是CDlgBase. 代码中假设须要创建一个对话框,一般建议使用DuiSystem类中封装的若干对话框相关的函数来操作,包括创建对话框.删除对话框.依据对话框名获取对话框指 ...

最新文章

  1. github总star超9K!一个超赞的 PyTorch 资源大列表,有人把它翻译成了中文版!
  2. 【POJ - 2373】Dividing the Path(单调队列优化dp)
  3. SpringBoot总结之CommandLineRunner
  4. Landsat8数据初识和概述
  5. 【国际篇】有关学术的一些小知识(EI、SCI、影响因子、中科院分区、JCR分区等)
  6. 数据可视化实战案例分享
  7. oracle中查看锁表,ORACLE中查看当前系统中锁表情况
  8. 数据集 过滤时 RecordCount 属性
  9. python习题20190130
  10. Python之3~20的乘积
  11. 判断图中有没有证件图片
  12. 11代i5 NUC使用记录
  13. EOJ - 我决不会TLE (一个智障的题目)
  14. vue-element-admin 的使用记录(三)
  15. Syclover战队专访 | 年度终局之战,键指圣诞狂欢
  16. 三、BLDC矢量控制基础知识:三相线圈的电感矩阵
  17. Linux下运行c++程序
  18. AutoFIS阅读总结
  19. iOS arc weak指针原理
  20. HbuilderX 无法运行项目到真机调试。

热门文章

  1. PiCIE: Unsupervised Semantic Segmentation Using Invariance and Equivariance in Clustering论文翻译
  2. Vue-弹层显示样式
  3. 局域网下两个电脑的文件传输--共享文件夹
  4. css 中动态获取宽度,css中的calc动态计算长度值
  5. python获取某博热搜数据并保存成Excel
  6. 关于Revit 材质 及 材质的外观图像 见解
  7. 史上最全Microsoft Edge 中的键盘快捷方式!(建议收藏!)
  8. 运算符重载(4.基本数据类型转换为类类型)【非重点】
  9. 只网签没备案 房管局能查到吗_怎么在房管局查询自己的房子有没备案
  10. 【C语言数组下标越界】数组下标越界引发的死循环