• [C++] Win32 API 的多线程Timer管理Trick


    有时候我们需要在程序里定时地完成一些任务, 比如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, 0, 0))
        {
            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 + 100, wParam, lParam); API向线程2发送一个创建定时器的消息WM_USER+100, 而wParamlParam则可以用来传递定时器的ID和延时信息(不传也可以, 看需求). 

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

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

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

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

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

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

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

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


    全部代码:

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

    #include <iostream>
    #include <Windows.h>
    
    using namespace std;
    
    DWORD WINAPI messageThread(LPVOID pM)
    {
        int cnt = 0;
        MSG msg;
        while (GetMessage(&msg, NULL, 0, 0))
        {
            switch (msg.message)
            {
            case WM_TIMER:
                cout<<"Timer "<<msg.wParam<<" is out!"<<endl;
                KillTimer(NULL, msg.wParam);
                break;
            case WM_USER + 100:                
                UINT_PTR timerID = SetTimer(NULL, msg.wParam, msg.lParam, (TIMERPROC)NULL);
                cout<<"Create timer "<<timerID<<endl;
                break;
            }
            DispatchMessage(&msg);  // dispatches message to window
        }
        return 0;
    }
    
    DWORD WINAPI testThread(LPVOID pM)
    {
        WPARAM wParam = 19990;
        LPARAM lParam = 500;
        DWORD idThread = (DWORD)pM;
        while (true) 
        {
            Sleep(2000);
            PostThreadMessage(idThread, WM_USER + 100, wParam, lParam);
            cout<<"Send msg to thread "<<idThread<<": Create timer of interval "<<lParam<<" Millisec."<<endl;
            wParam++;
        }
    
        return 0;
    }
    
    int main()
    {
        DWORD idThread;
        HANDLE createHandle = CreateThread(NULL, 0, &messageThread, NULL, 0, &idThread);    
        HANDLE testHandle = CreateThread(NULL, 0, &testThread, (LPVOID)idThread, 0, NULL);    
        WaitForSingleObject(createHandle, INFINITE);
        return 0;
    }
  • 相关阅读:
    C++与AS3
    基于Hadoop的大数据平台实施记——整体架构设计
    as3用鼠标拖动图形拼图——灰常简单的教程
    as3 与js相互通信
    cocos2d-x获取系统时间
    lucene3.6笔记添加搜索功能
    Ubuntu小私房(3)--Uubutnu启动美化大变身
    C++一维数组和指针的关系总结
    poj2486 Apple Tree (树形dp)
    opencv 中cvZero()的使用
  • 原文地址:https://www.cnblogs.com/lancelod/p/4188273.html
Copyright © 2020-2023  润新知