探讨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()自身可以传递参数的功能,而消除了(第二种)临时变量。
- 探讨Windows窗体程序执行周期性任务的几种方法
- Windows 自动定时执行任务的几种实现方法
- Windows 自动定时执行任务的几种实现方法
- Windows 任务计划 隐藏执行窗口的几种方法
- ruby执行周期性任务的三种gem介绍
- Android下定时执行特定任务的几种方法
- php定时任务执行的几种方法
- java指定延时执行任务的几种常见方法
- windows下不规则窗体创建的几种方法
- windows下不规则窗体创建的几种方法
- AS ScheduledExecutorService 周期性执行任务
- Android 进阶——实现周期性任务调度的几种攻略详解
- asp.net网站执行周期性任务的简易解决方法
- 内核中如何实现周期性的任务执行
- linux任务计划、周期性任务执行
- 任务顺序执行的几种方式
- Java中周期性任务执行--Timer
- ScheduledExecutorService执行周期性或定时任务
- android Graphics(二):路径及文字
- 判断是否为回文数
- 【树】【数论】[BZOJ1005][HNOI2008]明明的烦恼
- iOS 读书笔记
- iterator not incrementable 解决方案
- 探讨Windows窗体程序执行周期性任务的几种方法
- SimpleNews- Android MVP模式学习
- GIS+=地理信息+容器技术(6)——Dockerfile介绍
- android Graphics(三):区域(Range)
- apk反编译
- RunTime.getRunTime().addShutdownHook用法
- ubuntu中玩caffe里面的 MNIST
- android Graphics(四):canvas变换与操作
- 水仙花数