您的位置:首页 > 产品设计 > UI/UE

DuiVision开发教程(12)-任务类和任务队列

2015-05-21 23:44 330 查看
基于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;

// 类型必须大于0
void 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;       // 任务对象ID
LONG            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);
}
}
}else
if(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);
}
}
}else
if(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);
}
}
}else
if(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();
}
}
}
}else
if(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;              // 控件ID
CString     m_strControlName;   // 控件名
UINT        m_uMsg;             // 消息
WPARAM      m_wParam;           // 参数1
LPARAM      m_lParam;           // 参数2
CString     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 或搜索 蓝蚂蚁工作室
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: