有时候我们需要在程序里定时地完成一些任务, 比如5秒后发送, 10秒后弹窗之类的操作. 这就需要一个类似于定时器的组件. 这个组件在windows.h里被称为Timer.


设置一个Timer

第一步当然是设置一个Timer了, 在Win32 API里, 没有对象这个概念, 所以别指望像C#一样:

Timer t = new Timer(callback_func);
t.start();
/*...
...*/
t.stop();

当然上面只是一个例子, C#也并非真的是这样创建Timer的.

在Win32 API里, 我们使用SetTimer(hwnd, timerID, elapsedMilliSec, callbackFunc)来设置一个定时器, 下面摘抄修改自MSDN:

UINT_PTR WINAPI SetTimer(
 HWND hWnd,        
 UINT_PTR nIDEvent,
UINT uElapse,
TIMERPROC lpTimerFunc
);

参数:

hWnd [HWND]
定时器相关窗口的句柄. 窗口必须是创建定时器的线程所掌控的.

nIDEvent [UINT_PTR]
非零Timer标识符.

如果此参数是一个已经存在的定时器ID, 此定时器就会替换掉它.

如果此参数不是一个已经存在的定时器ID, 则此参数被忽略并重新生成一个TimerID;

如果不想替换掉Timer, 此参数应当为0并且hWnd参数为NULL.

uElapse [UINT] 
定时器超时时间, 以毫秒为单位.

如果此参数小于USER_TIMER_MINIMUM (0x0000000A), 则默认使用USER_TIMER_MINIMUM.

如果大于USER_TIMER_MAXIMUM (0x7FFFFFFF), 则默认使用USER_TIMER_MAXIMUM.
lpTimerFunc [TIMERPROC] 
超时时执行的函数的指针. 如果此参数为NULL, 系统则会向队列中发送一个WM_USER消息.

发送的消息MSG结构体的hwnd成员包含了hWnd参数.

返回值:

如果函数成功了, 返回值为新定时器的ID. 可以使用这个ID来删除这个Timer.
如果函数失败了, 返回值为0, 调用 GetLastError 来获取更多错误信息.

注意:

在窗口过程中包含一个WM_TIMER case表啊但是来处理WM_TIMER消息或在创建时使用自定义回调函数.

在创建Timer的线程, 你需要dispatch消息, 即使你使用的是自定义回调函数而不是WM_TIMER.

WM_TIMER的wParam参数即为TimerID - nIDEvent.

Timer的ID, nIDEvent, 是和窗口相关的. 另一个窗口可以有它自己的定时器, 且ID可以与其他窗口的定时器ID相同. 但定时器本身是独一无二的.

在hWnd为NULL的情况下, 本API可以重复使用定时器的ID.


定时器消息的处理:

    MSG msg;
while (GetMessage(&msg, NULL, , ))
{
switch (msg.message)
{
case WM_TIMER:
cout<<"Timer "<<msg.wParam<<" is out!"<<endl;
break;
}
DispatchMessage(&msg); // dispatches message to window
}

在一个进程的多个线程里, 每个线程都享有自己的独立的消息队列. 但是这就导致了一个问题: 很多时候创建定时器的线程我们不希望它被阻塞, 因为这可能是我们的工作线程.

举例来说, 我们使用线程1来监听所有的TCP连接, 那么这个线程本身就是阻塞在accept()方法上的, 这导致了它不能够实时地处理定时器的WM_TIMER消息. 因为GetMessage()方法本身也是阻塞的.

所以如果你需要在线程1每接收一个TCP Client就创建一个定时器, 但由于accept()本身的阻塞特性, 又不能在线程1中处理它, 从而只能在另一个线程 - 线程2里处理它, 怎么办?

首先, 不在线程1里创建Timer, 而是在需要创建定时器的时候(比如accept()成功), 使用 PostThreadMessage(idThread2, WM_USER + , wParam, lParam); API向线程2发送一个创建定时器的消息WM_USER+100, 而wParam和lParam则可以用来传递定时器的ID和延时信息(不传也可以, 看需求).

其中idThread2需要在线程2创建时获取, 保存:

DWORD idThread2;
CreateThread(NULL, , &thread2, NULL, , &idThread2);

并且线程2只做一件事: 循环阻塞调用 GetMessage()方法, 处理消息:

DWORD WINAPI messageThread(LPVOID pM)
{
int cnt = ;
MSG msg;
while (GetMessage(&msg, NULL, , ))
{
switch (msg.message)
{
case WM_TIMER:
cout<<"Timer "<<msg.wParam<<" is out!"<<endl;
       KillTimer(NULL, msg.wParam);
break;
case WM_USER + :
SetTimer(NULL, msg.wParam, msg.lParam, (TIMERPROC)NULL);
cout<<"Create timer "<<msg.wParam<<endl;
break;
}
DispatchMessage(&msg); // dispatches message to window
}
return ;
}

当检测到WM_TIMER消息时, 我们处理超时的情况;

当检测到WM_USER+100(即上文所说创建定时器)消息时, 我们创建一个新的定时器.

这样我们就实现了在一个线程里按需地创建定时器.

并且根据需求可以随时修改这个消息处理函数, 进行简单地拓展.


全部代码:

下面的代码每隔一定时间在testThread里向messageThread发送消息, 创建定时器(500ms), 并在messageThread中删除到期的定时器.

#include <iostream>
#include <Windows.h> using namespace std; DWORD WINAPI messageThread(LPVOID pM)
{
int cnt = ;
MSG msg;
while (GetMessage(&msg, NULL, , ))
{
switch (msg.message)
{
case WM_TIMER:
cout<<"Timer "<<msg.wParam<<" is out!"<<endl;
KillTimer(NULL, msg.wParam);
break;
case WM_USER + :
UINT_PTR timerID = SetTimer(NULL, msg.wParam, msg.lParam, (TIMERPROC)NULL);
cout<<"Create timer "<<timerID<<endl;
break;
}
DispatchMessage(&msg); // dispatches message to window
}
return ;
} DWORD WINAPI testThread(LPVOID pM)
{
WPARAM wParam = ;
LPARAM lParam = ;
DWORD idThread = (DWORD)pM;
while (true)
{
Sleep();
PostThreadMessage(idThread, WM_USER + , wParam, lParam);
cout<<"Send msg to thread "<<idThread<<": Create timer of interval "<<lParam<<" Millisec."<<endl;
wParam++;
} return ;
} int main()
{
DWORD idThread;
HANDLE createHandle = CreateThread(NULL, , &messageThread, NULL, , &idThread);
HANDLE testHandle = CreateThread(NULL, , &testThread, (LPVOID)idThread, , NULL);
WaitForSingleObject(createHandle, INFINITE);
return ;
}

[C++] Win32 API 的多线程Timer管理Trick - 利用PostThreadMessage的更多相关文章

  1. VC中多线程(一)Win32 API对多线程编程的支持

    http://blog.sina.com.cn/s/blog_4ae08ad801008yer.html

  2. Win32 API 多线程编程——一个简单实例(含消息参数传递)

    Win32 API进行程序设计具有很多优点:应用程序执行代码小,运行效率高,但是他要求程序员编写的代码较多,且需要管理所有系统提供给程序的资源,要求程序员对Windows系统内核有一定的了解,会占用程 ...

  3. 【C#】分享基于Win32 API的服务操作类(解决ManagedInstallerClass.InstallHelper不能带参数安装的问题)

    注:这里的服务是指Windows 服务. ------------------201508250915更新------------------ 刚刚得知TransactedInstaller类是支持带 ...

  4. C#中导入Win32 API函数

    C#中导入Win32 API的方法: 1.引用命名空间 using System.Net.Security; using System.Runtime.InteropServices; 2. [Dll ...

  5. C#调用Win32 api学习总结

    从.NET平台调用Win32 API Win32 API可以直接控制Microsoft Windows的核心,因为API(Application Programming Interface)本来就是微 ...

  6. Detours简介 (拦截x86机器上的任意的win32 API函数)

    Detours 当然是用detours,微软明显高腾讯一筹,同上,至今没失败过.写这种HOOK一定要再写个测试程序,不要直接HOOK你的目的程序,例如QQ,因为这样不方面更灵活的测试.说明一下:Det ...

  7. 【.Net】从.NET平台调用Win32 API

    小序        Win32 API可以直接控制Microsoft Windows的核心,因为API(Application Programming Interface)本来就是微软留给我们直接控制 ...

  8. MSComm控件与Win32 API操作串口有何区别?

    MSComm控件与Win32 API操作串口有何区别? [问题点数:50分,结帖人shell_shell]   收藏帖子 回复 我是一个小兵,在战场上拼命!   结帖率 83.33% 我以前用MSCo ...

  9. 深入浅出VC++串口编程之基于Win32 API

    1.API描述 在WIN32 API中,串口使用文件方式进行访问,其操作的API基本上与文件操作的API一致. 打开串口 Win32 中用于打开串口的API 函数为CreateFile,其原型为: H ...

随机推荐

  1. .net图表工具汇总

    概述:图形图表的可视化数据表现形式已成为一种趋势,本文推荐了10款非常好用的.NET图表控件,希望对广大.NET图表开发者能有所帮助. 读图时代,图形图表的可视化数据表现形式已成为一种趋势.因为图表能 ...

  2. 新手也能学会本地调试微信,natapp 官网映射

    本地调试微信的新手指引~ 照着配置,一定可以配置成功,实现本地调试微信,公司好几个同事按照我写的步骤,都独立配成功了. 1.首选在natapp注册一个账号,申请免费隧道或者购买隧道,我买了一个月9元的 ...

  3. POJ 2752 KMP中next数组的理解

    感觉这里讲的挺好的.http://cavenkaka.iteye.com/blog/1569062 就是不断递归next数组.长度不断减小. 题意:给你一个串,如果这个串存在一个长度为n的前缀串,和长 ...

  4. 使用Messenger 从Activity发送数据到service 通过后台计算结果Log输出;

    package com.lixu.messenger; import android.app.Activity; import android.app.Service; import android. ...

  5. TMemo Ctrl + A

    http://delphi.about.com/od/adptips2004/a/bltip0804_4.htm Here's how to implement the code for the CT ...

  6. 【笔记】《深入浅出MFC》第6章 MFC程序的生死因果

    一.头文件说明 STDAFX.H 这个文件用来作为Precompile header file,其内只是载入其他的MFC头文件.应用程序通常会准备自己的头STDAFX.H. AFXWIN.H 每一个W ...

  7. API - jQuery之操作cookie(转)

    Installation Include script after the jQuery library (unless you are packaging scripts somehow else) ...

  8. Oracle中varchar2类型字段长度限制使用问题

    为纪念中华人民共和国建军90周年,特此一篇,以此纪念,我军威武!!! 一.问题背景 项目中商品发布,却没有保存成功. 二.问题定位 初步判断向数据库中保存时出现了错误,查看日志文件,由于日志文件过大就 ...

  9. SSH项目搭建(三)——Maven多模块搭建项目

    多模块开发,大致的思想就是把一个项目按某种方式分成多个模块,再把模块们连接成一个整体,我们在开发的时候,可以很清晰的操作每一个模块,可以大大提高开发的效率. Java web项目,最常见的就是按代码的 ...

  10. webView的使用以及总结

    一.webview是什么? Android WebView 做为承载网页的载体控件,他在网页显示的过程中会产生一些事件,并回调给我们的应用程序,以便我们在网页加载过程中做应用程序想处理的事情.比如说客 ...