有时候我们需要在程序里定时地完成一些任务, 比如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. 『Scrapy』全流程爬虫demo

    建立好的爬虫工程如下: item.py 它用来存储解析后的响应文件: # -*- coding: utf-8 -*- # Define here the models for your scraped ...

  2. 2-16 MySQL字段约束-索引-外键

    一:字段修饰符 1:null和not null修饰符 我们通过这个例子来看看 mysql> create table worker(id int not null,name varchar(8) ...

  3. Hackintosh Power Management

    Also, be aware that hibernation (suspend to disk or S4 sleep) is not supported on hackintosh. You sh ...

  4. n转m进制标准写法(必须记忆)

    #include <bits/stdc++.h> using namespace std; int main() { int n,m; cin >> n >> m; ...

  5. Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level

    2018-03-23 18:32:21,690 [INFO] [http-nio-11007-exec-2] org.apache.coyote.http11.Http11Processor [Dir ...

  6. Qt经典—线程、事件与Qobject

    介绍 You’re doing it wrong. — Bradley T. Hughes 线程是qt channel里最流行的讨论话题之一.许多人加入了讨论并询问如何解决他们在运行跨线程编程时所遇到 ...

  7. Hive时间函数笔记

    unix_timestamp()函数: 返回值: bigint说明: 获得当前时区的UNIX时间戳 举例: hive> select unix_timestamp() from dual; 14 ...

  8. Python正则表达式操作指南

    摘要 本文是通过Python的 re 模块来使用正则表达式的一个入门教程,和库参考手册的对应章节相比,更为浅显易懂.循序渐进. 本文可以从 http://www.amk.ca/python/howto ...

  9. L1-015 跟奥巴马一起画方块

    美国总统奥巴马不仅呼吁所有人都学习编程,甚至以身作则编写代码,成为美国历史上首位编写计算机代码的总统.2014年底,为庆祝“计算机科学教育周”正式启动,奥巴马编写了很简单的计算机代码:在屏幕上画一个正 ...

  10. linux free 理解

    free命令:显示Linux系统中物理内存.buffer/cache.swap的使用情况. virtual-machine:~$ free total used free shared buffers ...