WM_TIMER优先级太低引发的问题

时间:2022-09-13 21:28:33
这里有个线程,以消息的形式接受其他进程传来的一个DWORD(放在wParam)并保存。这个消息(WM_PUT_DWORD)的接收很频繁,一秒最多可以有几千次。然后这个线程每隔一段时间要整理一下已保存的数据,我用WM_TIMER来触发这个行为。

不过仔细看了说明后发现,WM_TIMER消息的优先度很低,在GetMessage()时,要等队列里没有其他消息了才返回它。而队列里基本总是有其他进程发来的WM_PUT_DWORD消息的,所以WM_TIMER消息一般都要拖延很长时间才能从队列里取出,这就延误了整理的时机。


对于这个问题,我准备用内核对象Waitable Timer来代替WM_TIMER完成定时提示操作。不过内核和用户级别的切换很花时间,我担心要比WM_TIMER慢不少。而且消息和内核对象一起接受,就要用MsgWaitForMultipleObjects(),返回值的检查很麻烦,代码一大堆,看着很不舒服……

请问有没有什么让WM_TIMER能在这里正常工作的方法?或者其他可行又较快的方法?

23 个解决方案

#1


自己开线程,计算时间差等,满足条件了,就执行你的工作
把进程和线程的优先级都设置高点来提高优先级等

#2


GetMessage()和PeekMessage()可以设定消息过滤表,可以单独获取WM_TIMER消息。
然而要既获取WM_TIMER又要获取WM_PUT_DWORD,就要调用两次。虽然可行,但看着就是不舒服……

#3


引用 1 楼 oyljerry 的回复:
自己开线程,计算时间差等,满足条件了,就执行你的工作
把进程和线程的优先级都设置高点来提高优先级等


再开一个线程,那就要做数据同步工作了,那就更浪费时间了……

#4


在消息循环中加入:
if (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE))
{
// 响应定时
}

#5


引用 4 楼 cnzdgs 的回复:
在消息循环中加入:
if (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE))
{
// 响应定时
}


请看2楼。
有没有只调用一次的办法?

#6


WM_TIMER是不行的
办法很多,简单的办法是使用多媒体定时器timeSetEvent()函数,该函数定时精度为ms级。利用该函数可以实现周期性的函数调用,不同于WM_TIMER消息的是timeSetEvent方法是实时的,而WM_TIMER只是每隔一个周期加入一次消息排队。timeSetEvent()函数的原型如下: 
       MMRESULT timeSetEvent( UINT uDelay, 
                               UINT uResolution, 
                               LPTIMECALLBACK lpTimeProc, 
                               WORD dwUser, 
                               UINT fuEvent )
 成功后返回事件的标识符代码,否则返回NULL。函数的参数说明如下:
       uDelay:以毫秒指定事件的周期。
       Uresolution:以毫秒指定延时的精度,数值越小定时器事件分辨率越高。缺省值为1ms。
       LpTimeProc:指向一个回调函数。
       DwUser:回调函数所用参数。
       FuEvent:指定定时器事件类型:
       TIME_ONESHOT:uDelay毫秒后只产生一次事件
       TIME_PERIODIC :每隔uDelay毫秒周期性地产生事件。      
在定时器使用完毕后, 应及时调用timeKillEvent()将之释放。 
举例如下:
(1) 定义消息函数
void CALLBACK TimeProc(
   UINT uID,      
   UINT uMsg,     
   DWORD dwUser,  //传入的参数
   DWORD dw1,     
   DWORD dw2      
   )
{
g_i++;
PostMessage((HWND)dwUser,MSTIMER,NULL,NULL);
return ;
}
(2) 调用
if(m_uiTimerID==NULL) m_uiTimerID=timeSetEvent(50,1,(LPTIMECALLBACK)TimeProc,
(DWORD)this->GetSafeHwnd(),TIME_PERIODIC);
(3) 释放timeKillEvent(m_uiTimerID);

#7


如果要看着舒服,建议你不要用定时的方式,改成累计响应消息的个数,当个数达到一定数量后整理。

#8


The timeSetEvent function starts a specified timer event. The multimedia timer runs in its own thread. After the event is activated, it calls the specified callback function or sets or pulses the specified event object.

1. 不能在其他线程内执行整理操作;
2. timeSetEvent和SetWaitableTimer一样,也一定要切换用户和内核模式吧?

#9


引用 7 楼 cnzdgs 的回复:
如果要看着舒服,建议你不要用定时的方式,改成累计响应消息的个数,当个数达到一定数量后整理。


这个方法也考虑过。不过输入可能会突然中断,比如1000个消息后整理,结果只传来了999个就停了……

#10


引用 8 楼 fenghou1st 的回复:
The timeSetEvent function starts a specified timer event. The multimedia timer runs in its own thread. After the event is activated, it calls the specified callback function or sets or pulses the specified event object. 

1. 不能在其他线程内执行整理操作; 
2. timeSetEvent和SetWaitableTimer一样,也一定要切换用户和内核模式吧?

既然是整理工作,自然不会那么频繁,你其他线程计时到了,发送一个event等来通知,也不会消耗多少,这样也可以方便控制

#11


哦!想到了一个方法:创建一个线程计算时间,时间到了就向主线程发送自定义消息WM_CHECK。由于和WM_PUT_DWORD都是自定义消息,于是就没有优先级不同的困扰了。

好像可行!

#12


想了想,其实线程间切换和用户与内核模式切换都很花时间,而且切换线程好像花的更多……

#13


引用 11 楼 fenghou1st 的回复:
哦!想到了一个方法:创建一个线程计算时间,时间到了就向主线程发送自定义消息WM_CHECK。由于和WM_PUT_DWORD都是自定义消息,于是就没有优先级不同的困扰了。 

好像可行!

可以设置WM_CHECK级别比WM_PUT_DWORD更高

#14


感谢各位支招,暂时还是用Waitable Timer算了。
24小时后结帖。

#15


建议把需要传递的数据保存到队列,主线程定时去检查是否有新数据

#16


在消息队列中每一次循环自己的函数去找一次不就相当于优先级最高了吗。

#17


前边的回答都很次,自己看代码,不说了

LRESULT DoTimerMain(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam){
SetTimer (hWnd, TIMER_FLUSH, 30, TimerFlush) ;
SetTimer (hWnd, TIMER_SWAP_BUFFER, 20, TimerProc) ;
return 0;
}
VOID CALLBACK TimerFlush(HWND hWnd,UINT wMsg,UINT timerID,DWORD dwTime){ 
set_object(DWORD,dwTime,L"dwTime");
static bool firstrunflag=true;
if(firstrunflag){
SetTimer (hWnd, TIMER_SWAP_BUFFER, 2000, TimerProc) ;
firstrunflag=false;
return;
}
return;
}
void redraw(){
DWORD dwTime=get_object(DWORD,L"dwTime");
HWND hWnd=get_object(HWND,L"hwndMain");
TimerProc(hWnd,WM_TIMER,TIMER_SWAP_BUFFER,dwTime);
}

#18


加线程,GetTimeTicket

#19


友情up

#20


友情up

#21


引用 17 楼 luobonic 的回复:
前边的回答都很次,自己看代码,不说了
LRESULT DoTimerMain(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam){
    SetTimer (hWnd, TIMER_FLUSH, 30, TimerFlush) ;
    SetTimer (hWnd, TIMER_SWAP_BUFFER, 20, TimerProc) ;
    return 0;
}
VOID CALLBACK TimerFlush(HWND hWnd,UINT wMsg,UINT timerID,DWORD dwTime){ 
    set_object(DWORD,dwTime,L"dwTime");
    static bool firstrunflag=true;
    if(firstrunflag){
        SetTimer (hWnd, TIMER_SWAP_BUFFER, 2000, TimerProc) ;
        firstrunflag=false;
        return;
    }
    return;
}
void redraw(){
    DWORD dwTime=get_object(DWORD,L"dwTime");
    HWND hWnd=get_object(HWND,L"hwndMain");
    TimerProc(hWnd,WM_TIMER,TIMER_SWAP_BUFFER,dwTime);
}


太深奥了……能不能讲解一下?

TIMER_SWAP_BUFFER是每隔一段时间做的整理,但TIMER_FLUSH是什么?
TIMER_FLUSH的回调函数每次执行都保存回调时间,这个时间对redraw()有什么用?
redraw()是什么时候执行?为什么要自己发动TimerProc()?

顶楼的情况是,进程A给进程B发送大量自定义消息,进程B自己隔一段时间整理一次。

#22


引用 15 楼 jiangsheng 的回复:
建议把需要传递的数据保存到队列,主线程定时去检查是否有新数据


这样确实能避免消息优先级问题。不过工程对传出数据的进程A有要求,不能包含太多代码,因为进程A可能是一个已存在程序,它传出数据的行为是通过修改机器代码来做到的。
不知道除了消息,还有什么Windows机制能满足这个需求。

#23


想到了另一种方法:
还是使用WM_PUT_DWORD和WM_TIMER两消息,且只用一个线程。但额外记录上次WM_TIMER消息处理函数被调用的时间T1。

1. 在WM_PUT_DWORD消息处理函数中,先判断当前时间T0与T1的差,若大于WM_TIMER的周期T,则先执行WM_TIMER的处理函数,再继续自己的工作。
2. 在WM_TIMER消息处理函数中,先判断T0与T1的差,若小于T则直接返回。否则执行处理工作,最后更新T1。

这样,在消息队列拥挤的时候也能按时整理,而没有输入消息时也不会不执行整理。
额外开销是每处理一次输入调用一次GetTickCount()。

#1


自己开线程,计算时间差等,满足条件了,就执行你的工作
把进程和线程的优先级都设置高点来提高优先级等

#2


GetMessage()和PeekMessage()可以设定消息过滤表,可以单独获取WM_TIMER消息。
然而要既获取WM_TIMER又要获取WM_PUT_DWORD,就要调用两次。虽然可行,但看着就是不舒服……

#3


引用 1 楼 oyljerry 的回复:
自己开线程,计算时间差等,满足条件了,就执行你的工作
把进程和线程的优先级都设置高点来提高优先级等


再开一个线程,那就要做数据同步工作了,那就更浪费时间了……

#4


在消息循环中加入:
if (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE))
{
// 响应定时
}

#5


引用 4 楼 cnzdgs 的回复:
在消息循环中加入:
if (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE))
{
// 响应定时
}


请看2楼。
有没有只调用一次的办法?

#6


WM_TIMER是不行的
办法很多,简单的办法是使用多媒体定时器timeSetEvent()函数,该函数定时精度为ms级。利用该函数可以实现周期性的函数调用,不同于WM_TIMER消息的是timeSetEvent方法是实时的,而WM_TIMER只是每隔一个周期加入一次消息排队。timeSetEvent()函数的原型如下: 
       MMRESULT timeSetEvent( UINT uDelay, 
                               UINT uResolution, 
                               LPTIMECALLBACK lpTimeProc, 
                               WORD dwUser, 
                               UINT fuEvent )
 成功后返回事件的标识符代码,否则返回NULL。函数的参数说明如下:
       uDelay:以毫秒指定事件的周期。
       Uresolution:以毫秒指定延时的精度,数值越小定时器事件分辨率越高。缺省值为1ms。
       LpTimeProc:指向一个回调函数。
       DwUser:回调函数所用参数。
       FuEvent:指定定时器事件类型:
       TIME_ONESHOT:uDelay毫秒后只产生一次事件
       TIME_PERIODIC :每隔uDelay毫秒周期性地产生事件。      
在定时器使用完毕后, 应及时调用timeKillEvent()将之释放。 
举例如下:
(1) 定义消息函数
void CALLBACK TimeProc(
   UINT uID,      
   UINT uMsg,     
   DWORD dwUser,  //传入的参数
   DWORD dw1,     
   DWORD dw2      
   )
{
g_i++;
PostMessage((HWND)dwUser,MSTIMER,NULL,NULL);
return ;
}
(2) 调用
if(m_uiTimerID==NULL) m_uiTimerID=timeSetEvent(50,1,(LPTIMECALLBACK)TimeProc,
(DWORD)this->GetSafeHwnd(),TIME_PERIODIC);
(3) 释放timeKillEvent(m_uiTimerID);

#7


如果要看着舒服,建议你不要用定时的方式,改成累计响应消息的个数,当个数达到一定数量后整理。

#8


The timeSetEvent function starts a specified timer event. The multimedia timer runs in its own thread. After the event is activated, it calls the specified callback function or sets or pulses the specified event object.

1. 不能在其他线程内执行整理操作;
2. timeSetEvent和SetWaitableTimer一样,也一定要切换用户和内核模式吧?

#9


引用 7 楼 cnzdgs 的回复:
如果要看着舒服,建议你不要用定时的方式,改成累计响应消息的个数,当个数达到一定数量后整理。


这个方法也考虑过。不过输入可能会突然中断,比如1000个消息后整理,结果只传来了999个就停了……

#10


引用 8 楼 fenghou1st 的回复:
The timeSetEvent function starts a specified timer event. The multimedia timer runs in its own thread. After the event is activated, it calls the specified callback function or sets or pulses the specified event object. 

1. 不能在其他线程内执行整理操作; 
2. timeSetEvent和SetWaitableTimer一样,也一定要切换用户和内核模式吧?

既然是整理工作,自然不会那么频繁,你其他线程计时到了,发送一个event等来通知,也不会消耗多少,这样也可以方便控制

#11


哦!想到了一个方法:创建一个线程计算时间,时间到了就向主线程发送自定义消息WM_CHECK。由于和WM_PUT_DWORD都是自定义消息,于是就没有优先级不同的困扰了。

好像可行!

#12


想了想,其实线程间切换和用户与内核模式切换都很花时间,而且切换线程好像花的更多……

#13


引用 11 楼 fenghou1st 的回复:
哦!想到了一个方法:创建一个线程计算时间,时间到了就向主线程发送自定义消息WM_CHECK。由于和WM_PUT_DWORD都是自定义消息,于是就没有优先级不同的困扰了。 

好像可行!

可以设置WM_CHECK级别比WM_PUT_DWORD更高

#14


感谢各位支招,暂时还是用Waitable Timer算了。
24小时后结帖。

#15


建议把需要传递的数据保存到队列,主线程定时去检查是否有新数据

#16


在消息队列中每一次循环自己的函数去找一次不就相当于优先级最高了吗。

#17


前边的回答都很次,自己看代码,不说了

LRESULT DoTimerMain(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam){
SetTimer (hWnd, TIMER_FLUSH, 30, TimerFlush) ;
SetTimer (hWnd, TIMER_SWAP_BUFFER, 20, TimerProc) ;
return 0;
}
VOID CALLBACK TimerFlush(HWND hWnd,UINT wMsg,UINT timerID,DWORD dwTime){ 
set_object(DWORD,dwTime,L"dwTime");
static bool firstrunflag=true;
if(firstrunflag){
SetTimer (hWnd, TIMER_SWAP_BUFFER, 2000, TimerProc) ;
firstrunflag=false;
return;
}
return;
}
void redraw(){
DWORD dwTime=get_object(DWORD,L"dwTime");
HWND hWnd=get_object(HWND,L"hwndMain");
TimerProc(hWnd,WM_TIMER,TIMER_SWAP_BUFFER,dwTime);
}

#18


加线程,GetTimeTicket

#19


友情up

#20


友情up

#21


引用 17 楼 luobonic 的回复:
前边的回答都很次,自己看代码,不说了
LRESULT DoTimerMain(HWND hWnd,UINT wMsg,WPARAM wParam,LPARAM lParam){
    SetTimer (hWnd, TIMER_FLUSH, 30, TimerFlush) ;
    SetTimer (hWnd, TIMER_SWAP_BUFFER, 20, TimerProc) ;
    return 0;
}
VOID CALLBACK TimerFlush(HWND hWnd,UINT wMsg,UINT timerID,DWORD dwTime){ 
    set_object(DWORD,dwTime,L"dwTime");
    static bool firstrunflag=true;
    if(firstrunflag){
        SetTimer (hWnd, TIMER_SWAP_BUFFER, 2000, TimerProc) ;
        firstrunflag=false;
        return;
    }
    return;
}
void redraw(){
    DWORD dwTime=get_object(DWORD,L"dwTime");
    HWND hWnd=get_object(HWND,L"hwndMain");
    TimerProc(hWnd,WM_TIMER,TIMER_SWAP_BUFFER,dwTime);
}


太深奥了……能不能讲解一下?

TIMER_SWAP_BUFFER是每隔一段时间做的整理,但TIMER_FLUSH是什么?
TIMER_FLUSH的回调函数每次执行都保存回调时间,这个时间对redraw()有什么用?
redraw()是什么时候执行?为什么要自己发动TimerProc()?

顶楼的情况是,进程A给进程B发送大量自定义消息,进程B自己隔一段时间整理一次。

#22


引用 15 楼 jiangsheng 的回复:
建议把需要传递的数据保存到队列,主线程定时去检查是否有新数据


这样确实能避免消息优先级问题。不过工程对传出数据的进程A有要求,不能包含太多代码,因为进程A可能是一个已存在程序,它传出数据的行为是通过修改机器代码来做到的。
不知道除了消息,还有什么Windows机制能满足这个需求。

#23


想到了另一种方法:
还是使用WM_PUT_DWORD和WM_TIMER两消息,且只用一个线程。但额外记录上次WM_TIMER消息处理函数被调用的时间T1。

1. 在WM_PUT_DWORD消息处理函数中,先判断当前时间T0与T1的差,若大于WM_TIMER的周期T,则先执行WM_TIMER的处理函数,再继续自己的工作。
2. 在WM_TIMER消息处理函数中,先判断T0与T1的差,若小于T则直接返回。否则执行处理工作,最后更新T1。

这样,在消息队列拥挤的时候也能按时整理,而没有输入消息时也不会不执行整理。
额外开销是每处理一次输入调用一次GetTickCount()。