探讨Windows窗体程序执行周期性任务的几种方法

来源:互联网 发布:正在安装网络组件特曼 编辑:程序博客网 时间:2024/06/07 02:17

Overview

探讨Windows窗体程序执行周期性任务的几种方法,涉及定时器方法、WaitFor方法等。

故事

假设是一个对话框应用程序,用户在对话框上单击了Start之后,就启动周期性的一项任务。

为了简化问题,假定在整个过程中不关闭对话框。——如果要关闭,只需要发消息或直接函数调用中断任务即可。不过本文不讨论这些方面。

UI

UI可以简化为,只有一个Start按钮。这个对话框对应于class CPeriodTaskDlg。

IDD_PERIODTASK_DIALOG DIALOGEX 0, 0, 184, 59STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENUEXSTYLE WS_EX_APPWINDOWCAPTION "PeriodTask"FONT 8, "MS Shell Dlg", 0, 0, 0x1BEGIN    PUSHBUTTON      "Start",IDC_START,25,17,117,20END

任务执行体

假定由class CFoo来执行具体的周期性任务。

任务假定为每隔2s弹出一个对话框,5次后任务结束。

定时器方案

定时器是一种很好地周期性触发任务的机制。对应的SetTimer或KillTimer的第一个参数是HWND,如果CFoo不是一个窗体,那么可以借用CPeriodTaskDlg来完成。具体地,

  • CFoo在开始执行任务的时候,用SetTimer创建定时器;窗体指向CPeriodTaskDlg;
  • 窗体处理WM_TIMER消息,检测到是CFoo创建的定时器,则调用CFoo的执行周期性任务的函数。

下面给出示例代码。

CFoo

头文件:

#pragma onceclass CFoo{public:    explicit CFoo(CWnd *pWnd);    ~CFoo(void);    void Start();    void Doit();    enum {FOO_TIMER_ID = 100};private:    int m_iCounter;    CWnd *m_pWnd;};

实现文件:

#include "StdAfx.h"#include "Foo.h"CFoo::CFoo(CWnd *pWnd): m_iCounter(0), m_pWnd(pWnd){}CFoo::~CFoo(void){}void CFoo::Start(){    Doit();    ::SetTimer(m_pWnd->m_hWnd, FOO_TIMER_ID, 2000, NULL);}void CFoo::Doit(){    if (m_iCounter < 5) {        ::AfxMessageBox("Do something ...");        m_iCounter++;        return;    }    ::AfxMessageBox("Something has been done. Kill the timer ...");    KillTimer(m_pWnd->m_hWnd, FOO_TIMER_ID);}

CPeriodTaskDlg

省略掉部分自动生成的代码。

头文件:

#include "Foo.h"class CPeriodTaskDlg : public CDialogEx{    // ..........    afx_msg void OnBnClickedStart();    afx_msg void OnTimer(UINT_PTR nIDEvent);    CFoo m_foo;};

实现文件:

CPeriodTaskDlg::CPeriodTaskDlg(CWnd* pParent /*=NULL*/)    : CDialogEx(CPeriodTaskDlg::IDD, pParent), m_foo(this){    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);}BEGIN_MESSAGE_MAP(CPeriodTaskDlg, CDialogEx)    ON_WM_SYSCOMMAND()    ON_WM_PAINT()    ON_WM_QUERYDRAGICON()    ON_BN_CLICKED(IDC_START, &CPeriodTaskDlg::OnBnClickedStart)    ON_WM_TIMER()END_MESSAGE_MAP()// ...........void CPeriodTaskDlg::OnBnClickedStart(){    m_foo.Start();}void CPeriodTaskDlg::OnTimer(UINT_PTR nIDEvent){    if (CFoo::FOO_TIMER_ID == nIDEvent) {        m_foo.Doit();    }    CDialogEx::OnTimer(nIDEvent);}

讨论

这种方法中,CFoo借用了CPeriodTaskDlg的窗口及其定时器消息处理。其实这种交互(相互配合)的机制是比较清晰的,可能看起来两者有点强耦合,——毕竟为了这种配合添加了不少成员函数(如m_pWnd)和相互调用。事实上,CFoo本身要做的事情,让CPeriodTaskDlg帮忙了,即没有真正实现职责唯一性准则。

无论如何,代码易懂,维护成本也可接受。

无窗体定时器

SetTimer和KillTimer的第一个参数HWND其实也可以传入NULL。但这个时候,CFoo就需要自己定义一个定时器处理函数(TIMERPROC/静态成员函数)。——CFoo还不是那么轻量级地可以把自身对象传给这个静态成员函数,这里借用了一个临时(模块级)变量。

CFoo

注意此时SetTimer()会生成一个定时器ID,后续KillTimer需要传入这个ID。

头文件:

#pragma onceclass CFoo{public:    CFoo();    ~CFoo(void);    void Start();    void Doit();    static void CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);    enum {FOO_TIMER_ID = 100};private:    int m_iCounter;    UINT_PTR m_iTimerID;};

实现文件:

#include "StdAfx.h"#include "Foo.h"static CFoo *m_pTemp = NULL;CFoo::CFoo(): m_iCounter(0), m_iTimerID(0){}CFoo::~CFoo(void){}void CFoo::Start(){    m_pTemp = this;    Doit();    m_iTimerID = ::SetTimer(NULL, 0/*FOO_TIMER_ID*/, 2000, &CFoo::TimerProc);}void CALLBACK CFoo::TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime){    m_pTemp->Doit();}// refactoredvoid CFoo::Doit(){    m_iCounter++;    ::AfxMessageBox("Do something ...");    if (m_iCounter < 5) return;    ::AfxMessageBox("Something has been done. Kill the timer ...");    KillTimer(NULL, m_iTimerID/*FOO_TIMER_ID*/);}

CPeriodTaskDlg

头文件:

#pragma once#include "Foo.h"class CPeriodTaskDlg : public CDialogEx{    // ...........    afx_msg void OnBnClickedStart();    CFoo m_foo;};

实现文件:

CPeriodTaskDlg::CPeriodTaskDlg(CWnd* pParent /*=NULL*/)    : CDialogEx(CPeriodTaskDlg::IDD, pParent), m_foo(){    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);}void CPeriodTaskDlg::OnBnClickedStart(){    m_foo.Start();}

讨论

这种方式比前一种更加松耦合。——只是CFoo中借用了一个临时变量,让静态成员函数能够取到对象的值。

WaitFor+CreateThread

另一种实现周期性的机制是WaitFor,但WaitFor会阻塞,所以这里搭配上线程调用。在对话框调用CFoo的Start的时候,由Start创建一个线程去执行周期性的任务,而Start()立即返回。

CFoo

头文件:

#pragma onceclass CFoo{public:    CFoo();    ~CFoo(void);    void Start();    void Doit();    static DWORD WINAPI ThreadProc(LPVOID lpThreadParameter);private:    //int m_iCounter;};

实现文件:

#include "StdAfx.h"#include "Foo.h"CFoo::CFoo()//: m_iCounter(0){}CFoo::~CFoo(void){}void CFoo::Start(){    HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc, this, 0, NULL);}DWORD WINAPI CFoo::ThreadProc(LPVOID lpThreadParameter) {    CFoo* foo = (CFoo*)lpThreadParameter;    HANDLE hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);    for (int iCounter = 1; iCounter <= 5; iCounter++) {        foo->Doit();        if (iCounter == 5) break; // end immediately        ::WaitForSingleObject(hEvent, 2000);    }    ::AfxMessageBox("Something has been done.");    return 0;}void CFoo::Doit(){    ::AfxMessageBox("Do something ...");}

删除线程对象

上面的代码可以运行得很好,不过作为习惯,最好等待线程结束,并CloseHandle。

void CFoo::Start(){    HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc, this, 0, NULL);    WaitForSingleObject(hThread, INFINITE);    ::AfxMessageBox("Thread end.");    CloseHandle(hThread);}

不过需要注意的是,直接用这里的方法会阻塞函数调用,所以可以把线程句柄设计成类的成员变量,然后在类的其他合适的成员函数中添加这些代码。

CPeriodTaskDlg

和第二种方式一样,代码略。

讨论

这种方式看起来更加流畅一些,借由CreateThread()自身可以传递参数的功能,而消除了(第二种)临时变量。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 联想手机升级系统失败了怎么办 日本语言学校申请研究生签证怎么办 在埃塞俄比亚签证过期了怎么办 看完的小说想要卖掉该怎么办 在俄罗斯脸干了痛怎么办 苹果手表电池放亏了怎么办 平板电脑电池不耐用怎么办 计算机电池放入后没反应怎么办 笔记本电脑玩游戏花屏怎么办 笔记本玩游戏花屏怎么办 笔记本电脑充电插口坏了怎么办 车蓄电池没电了怎么办 汤浅q85电瓶亏电怎么办 富士康自离行李怎么办 微店红酒食品认证怎么办 yy频道提示禁止游客进入怎么办 口红颜色太艳了怎么办 我在菲律宾想走怎么办 当国家流通货币不够用怎么办 苹果5s账号密码忘记怎么办 钢铁雄心3补给不足怎么办 灯外观颜色太难看了怎么办 被移民公司骗了怎么办 文明5大包锁区怎么办 鸭子被黄鼠狼叼走了怎么办 黄鼠狼再店了拉屎怎么办 我只有信用卡但是又想去嫖怎么办 瑞士退税单掉了怎么办 装了新风噪音大怎么办 意大利 护照被偷了怎么办 请问去意大利要怎么办护照 在意大利护照丢了怎么办 考研二战档案打回原籍怎么办 脸上的肉往下掉怎么办 眼镜带了往下掉怎么办 孩子捅别的孩子眼睛了怎么办 眼睛不小心捅伤怎么办 我的爸爸是小偷怎么办 违停罚款忘记交怎么办 顺风车无人接单怎么办 来例假腰特别疼怎么办