WM_TIMER消息在线程被阻塞时的系统处理
2010-08-20 20:16
344 查看
我的脑海中忽然对这样一个问题有一些模糊,也就是当一个安装了定时器的线程被阻塞期间,定时器消息如何被送往消息队列?在线程从阻塞状态恢复以后,消息队列的状态是怎么样的?是否里面聚集多个WM_TIMER消息?还是阻塞期间没有收到WM_TIMER消息,还是在阻塞期间多个应该送达的WM_TIMER被合并成了一个?(类似WM_PAINT消息那样)。
所以我做了一个小实验来验证这个问题,结果我发现结论是最后一种情况,即可能系统在被唤起应该像某个线程的消息队列投递WM_TIMER消息时,它如果发现消息队列中已经有相同的WM_TIMER消息(ID号相同),则可能放弃投递,否则才会投递。这样就符合我们观察到的结果,即阻塞期间应该产生的多个定时器消息看起来仿佛被合并成了一个。
这个试验是这样的,我给UI线程安装一个5秒钟间隔的定时器(收到定时器消息后在窗口进行输出),然后发起另一个线程,阻塞 UI 线程21秒的时间。然后观察UI线程的输出,效果如下:
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会严格按照响应时间产生(即不要认为一定能产生相应的数量),不要认为每一条一定会在某个时刻得到处理(即其被处理的时间也取决于线程的运行状态,例如被阻塞所拖延)。可以认为在阻塞期间的所有定时器消息仅会在线程停止阻塞后处理一次。
所以我做了一个小实验来验证这个问题,结果我发现结论是最后一种情况,即可能系统在被唤起应该像某个线程的消息队列投递WM_TIMER消息时,它如果发现消息队列中已经有相同的WM_TIMER消息(ID号相同),则可能放弃投递,否则才会投递。这样就符合我们观察到的结果,即阻塞期间应该产生的多个定时器消息看起来仿佛被合并成了一个。
这个试验是这样的,我给UI线程安装一个5秒钟间隔的定时器(收到定时器消息后在窗口进行输出),然后发起另一个线程,阻塞 UI 线程21秒的时间。然后观察UI线程的输出,效果如下:
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会严格按照响应时间产生(即不要认为一定能产生相应的数量),不要认为每一条一定会在某个时刻得到处理(即其被处理的时间也取决于线程的运行状态,例如被阻塞所拖延)。可以认为在阻塞期间的所有定时器消息仅会在线程停止阻塞后处理一次。
相关文章推荐
- WM_TIMER消息在线程被阻塞时的系统处理
- 利用主线程与子线程间的消息通讯,实现任务处理队列.子线程中创建不会阻塞执行的窗口
- vc/mfc 进程消息队列,线程消息队列,和系统消息队列,该如何处理
- 程序缩小到托盘后系统就无法关机(解决方案)——处理WM_QUERYENDSESSION消息,并把它标识为处理过了
- Android系统中异步消息处理线程机制的理解
- 【VS2010学习笔记】【函数学习】一(MFC+OpenCV2.4.7读取摄像头之WM_TIMER消息处理函数的添加问题)
- Android 消息处理系统 Handler的一些介绍
- 深入理解Android消息处理系统——Looper、Handler、Thread
- 基于Handler的消息处理线程的缺点
- 处理鼠标离开窗口的消息 (WM_MOUSELEAVE)
- 消息循环中的TranslateMessage函数和DispatchMessage函数,特别注意WM_TIMER消息
- 一共81个,开源大数据处理工具汇总:查询引擎、流式计算、迭代计算、离线计算、键值存储、表格存储、文件存储、资源管理、日志收集系统、消息系统、分布式服务、集群管理、基础设施、搜索引擎、数据挖掘=监控
- Delphi对WM_NCHITTEST消息的处理
- 深入理解Android消息处理系统——Looper、Handler、Thread
- android中的线程与消息处理
- Android异步消息处理线程之----Looper+MessageQueue+Handler
- WM_TIMER消息莫名失踪
- (转)深入理解Android消息处理系统——Looper、Handler、Thread
- 消息驱动与线程处理
- Linux定时器处理之实时信号使用,消息队列阻塞模型,避免超时等待