WM_TIMER消息在线程被阻塞时的系统处理

简介:     我的脑海中忽然对这样一个问题有一些模糊,也就是当一个安装了定时器的线程被阻塞期间,定时器消息如何被送往消息队列?在线程从阻塞状态恢复以后,消息队列的状态是怎么样的?是否里面聚集多个WM_TIMER消息?还是阻塞期间没有收到WM_TIMER消息,还是在阻塞期间多个应该送达的WM_TIMER被合并成了一个?(类似WM_PAINT消息那样)。

    我的脑海中忽然对这样一个问题有一些模糊,也就是当一个安装了定时器的线程被阻塞期间,定时器消息如何被送往消息队列?在线程从阻塞状态恢复以后,消息队列的状态是怎么样的?是否里面聚集多个WM_TIMER消息?还是阻塞期间没有收到WM_TIMER消息,还是在阻塞期间多个应该送达的WM_TIMER被合并成了一个?(类似WM_PAINT消息那样)。

    所以我做了一个小实验来验证这个问题,结果我发现结论是最后一种情况,即可能系统在被唤起应该像某个线程的消息队列投递WM_TIMER消息时,它如果发现消息队列中已经有相同的WM_TIMER消息(ID号相同),则可能放弃投递,否则才会投递。这样就符合我们观察到的结果,即阻塞期间应该产生的多个定时器消息看起来仿佛被合并成了一个。

    这个试验是这样的,我给UI线程安装一个5秒钟间隔的定时器(收到定时器消息后在窗口进行输出),然后发起另一个线程,阻塞 UI 线程21秒的时间。然后观察UI线程的输出,效果如下:

    

    

 

    我们绘制一个更直观的的图形来解释上面的输出,如下:

    

        

 

    图中,红色的箭头是UI线程实际收到的定时器消息,蓝色箭头是阻塞期间应该产生消息的时间。可见,在阻塞期间应该产生 4 个 WM_TIMER 消息,但实际上在线程从阻塞状态恢复后,立即处理了仅仅一条。此后消息仍然按照既定间隔定期发送。

 

    上文中用于测试的代码如下:

 

 

img_405b18b4b6584ae338e0f6ecaf736533.gif CODE_WM_TIMER_TEST
#include  " stdafx.h "
#include 
< string >
#include 
" resource.h "
using   namespace  std;

HINSTANCE hInst;
string  m_msg;
int  blockTime;

LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
// 测试线程
DWORD WINAPI TestThread( void *  pArg);

int  APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     
int        nCmdShow)
{
     
//  TODO: Place code here.
    hInst  =  hInstance;
    DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)MyDlgProc, 
0 );
    
return   0 ;
}

//  对话框
LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    
static   int  b_timerOn;
    
static   int  timerNum;
    
switch (message)
    {
    
case  WM_INITDIALOG:
        b_timerOn 
=   0 ;
        timerNum 
=   0 ;
        
break ;
    
case  WM_COMMAND:
        {
            WORD ctl 
=  LOWORD(wParam);
            
switch (ctl)
            {
            
case  IDOK:
            
case  IDCANCEL:
                EndDialog(hDlg, ctl);
                
return  TRUE;
            
case  IDC_BT_SETTIMER:
                
if (b_timerOn)
                {
                    SetDlgItemText(hDlg, ctl, 
" 开始计时器 " );
                    KillTimer(hDlg, 
1 );
                }
                
else
                {
                    SetDlgItemText(hDlg, ctl, 
" 停止计时器 " );
                    SetTimer(hDlg, 
1 5000 , NULL);
                }
                b_timerOn 
^=   1 // 取反(在0,1之间切换)
                 return  TRUE;

            
case  IDC_BT_STARTTHREAD:
                {
                    DWORD threadId;
                    SYSTEMTIME st;
                    blockTime 
=   21000 ;
                    
char  line[ 128 ];
                
                    HANDLE hThread 
=  CreateThread(NULL,  0 ,
                        TestThread, 
                        (LPVOID)
& blockTime,
                        
0 // 立即执行
                         & threadId
                        );

                    
// sprintf(line, "thread: %ld start...\r\n", threadId);
                    
// m_msg += line;
                    
// SetDlgItemText(hDlg, IDC_MSG, m_msg.c_str());

                    
// 阻塞主线程
                    WaitForSingleObject(hThread, INFINITE);
                    CloseHandle(hThread);

                    GetLocalTime(
& st);
                    sprintf(line, 
" %02d:%02d: thread: %ld exit...\r\n " , st.wMinute, st.wSecond, threadId);
                    m_msg 
+=  line;
                    SetDlgItemText(hDlg, IDC_MSG, m_msg.c_str());
                }
                
return  TRUE;
            }
        }
        
break ;
    
case  WM_TIMER:
        {
            SYSTEMTIME st;
            
char  text[ 96 ];
            GetLocalTime(
& st);
            sprintf(text, 
" %02d:%02d WM_TIMER_%04ld\r\n " , st.wMinute, st.wSecond, timerNum);
            timerNum
++ ;

            m_msg 
=  m_msg  +  text;
            SetDlgItemText(hDlg, IDC_MSG, m_msg.c_str());
        }
        
return  TRUE;
    
case  WM_DESTROY:
        KillTimer(hDlg, 
1 );
        
return  TRUE;
    }
    
return  FALSE;
}



// 测试线程
DWORD WINAPI TestThread( void *  pArg)
{
    
int *  pBlockTime  =  ( int * )pArg;
    Sleep(
* pBlockTime);
    
return   0 ;
}

 

    结论:根据上面的观察,可以认为,定时器消息和 绘制消息类似,在进程被阻塞期间,多个定时器消息可能被系统透明的合并成了一条消息。在从阻塞状态恢复后,定时器扔按照原有间隔继续发送。

 

    请注意,不要认为所有WM_TIMER会严格按照响应时间产生(即不要认为一定能产生相应的数量),不要认为每一条一定会在某个时刻得到处理(即其被处理的时间也取决于线程的运行状态,例如被阻塞所拖延)。可以认为在阻塞期间的所有定时器消息仅会在线程停止阻塞后处理一次。

 

 

 

目录
相关文章
|
6月前
|
消息中间件 存储 缓存
【嵌入式软件工程师面经】Linux系统编程(线程进程)
【嵌入式软件工程师面经】Linux系统编程(线程进程)
127 1
|
4月前
|
监控 Java 测试技术
Java并发编程最佳实践:设计高性能的多线程系统
Java并发编程最佳实践:设计高性能的多线程系统
66 1
|
2月前
|
监控 安全 算法
线程死循环确实是多线程编程中的一个常见问题,它可能导致应用程序性能下降,甚至使整个系统变得不稳定。
线程死循环是多线程编程中常见的问题,可能导致性能下降或系统不稳定。通过代码审查、静态分析、日志监控、设置超时、使用锁机制、测试、选择线程安全的数据结构、限制线程数、使用现代并发库及培训,可有效预防和解决死循环问题。
62 1
|
2月前
|
监控 安全 算法
线程死循环是多线程编程中的常见问题,可能导致应用性能下降甚至系统不稳定。
【10月更文挑战第6天】线程死循环是多线程编程中的常见问题,可能导致应用性能下降甚至系统不稳定。为了解决这一问题,可以通过代码审查、静态分析、添加日志监控、设置超时机制、使用锁和同步机制、进行全面测试、选用线程安全的数据结构、限制线程数量、利用现代并发库,并对团队进行培训等方法来预防和减少死循环的发生。尽管如此,多线程编程的复杂性仍需要持续监控和维护以确保系统稳定。
53 3
【多线程面试题十二】、阻塞线程的方式有哪些?
线程阻塞的方式包括调用sleep()方法、阻塞式IO操作、等待同步监视器的获取、等待通知(notify),以及慎用的suspend()方法。
|
5月前
|
算法 Java 编译器
多线程线程安全问题之系统层面的锁优化有哪些常见的策略
多线程线程安全问题之系统层面的锁优化有哪些常见的策略
|
4月前
|
编译器 C语言 iOS开发
iOS 16 系统键盘修复问题之确定_lock是否用于保护对_deferredTasks的多线程读写如何解决
iOS 16 系统键盘修复问题之确定_lock是否用于保护对_deferredTasks的多线程读写如何解决
|
5月前
|
设计模式 存储 安全
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
62 1
|
6月前
|
存储 测试技术
【工作实践(多线程)】十个线程任务生成720w测试数据对系统进行性能测试
【工作实践(多线程)】十个线程任务生成720w测试数据对系统进行性能测试
62 0
【工作实践(多线程)】十个线程任务生成720w测试数据对系统进行性能测试
|
6月前
|
前端开发 JavaScript
JavaScript异步处理避免了单线程阻塞,如回调函数、Promise和async/await。
【6月更文挑战第22天】JavaScript异步处理避免了单线程阻塞,如回调函数、Promise和async/await。回调是基础,用于在操作完成后执行函数;Promise管理异步状态,支持链式调用;async/await提供同步代码外观,简化错误处理。每种技术在处理耗时任务时都起着关键作用。
53 3