MFC timeSetEvent() 定时器的简单使用*

Posted 波雅_汉库克

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MFC timeSetEvent() 定时器的简单使用*相关的知识,希望对你有一定的参考价值。

实验的效果


按下定时器开始显示累加数字,再按下暂停,再按下继续.就这么一个简单的功能.

需要一个button和一个静态框,这里就不赘述了.button下的代码:

void CtimerDlg::OnBnClickedTimer()

	// TODO: Add your control notification handler code here
	CtimerApp* app = static_cast<CtimerApp*>(AfxGetApp());
	app->SetTimer();

很简单就是在项目类中设置计时器.看看SetTimer()函数.

void CtimerApp::SetTimer()

	m_bBlag = !m_bBlag;
	if (m_bBlag)
	
		wTimerRes = 100;
		timer = timeSetEvent(500, 10, callBackTimer, wTimerRes, TIME_PERIODIC);
	
	else
	
		timeEndPeriod(wTimerRes);
		timeKillEvent(timer);
	

这个就是定时器的实现.
思路就是按下去创建定时器,再按下删除定时器.
重点介绍一下timeSetEvent()这个定时器函数,这是个win32的函数,所以直接用是用不了的,需要在头文件中添加引用.

#include <windows.h>
#pragma comment(lib,"Winmm.lib")
#include <MMSystem.h>

我在timer.h中添加了这个,不添加这个是编译不过去,报2019和2001错误.

在回头细看一下这个函数.

 
MMRESULT timeSetEvent( UINT uDelay, 
                             				    UINT uResolution, 
                                 				LPTIMECALLBACK lpTimeProc, 
                              				   WORD dwUser, 
                               				  UINT fuEvent )
 
        uDelay:以毫秒指定事件的周期。
         Uresolution:以毫秒指定延时的精度,数值越小定时器事件分辨率越高。缺省值为1ms。
         LpTimeProc:指向一个回调函数。
         DwUser:存放用户提供的回调数据。
         FuEvent:指定定时器事件类型:
         TIME_ONESHOT:uDelay毫秒后只产生一次事件
         TIME_PERIODIC :每隔uDelay毫秒周期性地产生事件。

第一个参数是事件周期,也就是定时多长事件.第二个是精度,第三个是回调函数,也就是定时器到了要执行的函数,这个函数是有要求的,下面重点说.第四个参数是给回调函数传递的值,第五个参数是产生的事件.

重点说一下回调函数.

void CALLBACK callBackTimer(UINT wTimerID, UINT msg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2);

这个函数一定是个全局函数,而且一定要写成这样的,函数名字可以变,其他的一律不能动.因为这是个函数指针,函数签名是定死的.

typedef void (CALLBACK TIMECALLBACK)(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2);
typedef TIMECALLBACK FAR *LPTIMECALLBACK;

这是timeSetEvent()函数的第三个参数的定义,他的返回值和参数都定死了,随意改是不行的.

回调函数的内容:

void CALLBACK callBackTimer(UINT wTimerID, UINT msg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
	
	auto a = dwUser;
	CtimerApp* app = static_cast<CtimerApp*>(AfxGetApp());
	CString str;
	str.Format(L"%d",app->num);	
	app->m_pMainWnd->GetDlgItem(IDC_STATIC)->SetWindowTextW(str);
	app->num++;

这个没什么说的,重点是怎么用定时器.
说个细节就是auto a = dwUser; dwUser这个参数就是之前timeSetEvent() 这个函数的第四个参数传进来的参数.虽然这个类型是个unsigned long的我觉得也可以传递指针,毕竟指针也是16进制的数.没有尝试,有兴趣可以试试.

用完了怎么把这个定时器,怎么删除.

		timeEndPeriod(wTimerRes);
		timeKillEvent(timer);

这就是删除定时器的函数,第一个函数是释放给回调函数的值,第二个函数是释放timeSetEvent()定时器函数返回的值,返回值是UINT型的.

Windows 中的可等待计时器问题(timeSetEvent 和 CreateTimerQueueTimer)

【中文标题】Windows 中的可等待计时器问题(timeSetEvent 和 CreateTimerQueueTimer)【英文标题】:Problem with waitable timers in Windows (timeSetEvent and CreateTimerQueueTimer) 【发布时间】:2010-03-25 13:41:51 【问题描述】:

我的应用程序需要高分辨率(比 1 毫秒更准确)的计时。 Windows 中的可等待计时器(或可以制成)精确到毫秒,但如果我需要一个精确的周期,例如 35.7142857141 毫秒,即使是 36 毫秒周期的可等待计时器也会很快失去同步。

我对这个问题的“解决方案”(带有讽刺意味的引号,因为它工作不正常)是使用一系列一次性计时器,我使用每个计时器的到期时间来调用下一个计时器。通常这样的过程会随着时间的推移而累积错误,但在每个计时器回调中,我检查当前时间(使用System.Diagnostics.Stopwatch)并使用它来计算下一个计时器的周期需要是多少(所以如果一个计时器碰巧过期有点晚,下一个计时器会自动有更短的时间来补偿)。

这按预期工作,除了可能在 10 到 15 秒后,计时器系统似乎陷入困境,并且这里和那里的一些计时器回调延迟了 25 到 100 毫秒。几秒钟后问题消失了,一切又顺利运行了 10-15 秒,然后又出现了口吃。

由于我使用Stopwatch 设置每个定时器周期,我还使用它来监控每个定时器回调的到达时间。在平稳运行期间,大多数(可能 95%)间隔为 35 或 36 毫秒,并且没有任何间隔与预期的 35.7142857143 相差超过 5 毫秒。

在“故障”伸展期间,间隔的分布几乎相同,除了极少数非常大(在可能 3 秒的伸展期间,有几个超过 60 毫秒,有一个或两个超过 100 毫秒) )。这种口吃非常明显,如果可能的话,这就是我正在尝试解决的问题。

对于高分辨率计时器,我使用了来自winmm.dll 的极其古老的timeSetEvent() 多媒体计时器。为了解决这个问题,我改用CreateTimerQueueTimer(连同timeBeginPeriod 来设置高分辨率),但我发现两种计时器机制都存在同样的问题。我尝试使用CreateTimerQueueTimer 的各种标志进行试验,这些标志确定计时器在哪个线程上运行,但无论如何都会出现口吃。

这只是以这种方式使用计时器的一个基本问题(即使用每个一次性计时器来调用下一个计时器)吗?如果是这样,我还有其他选择吗?我正在考虑的一件事是,在我需要重置计时器之前,确定有多少个连续的 1 毫秒精度的滴答声将使我保持在某个任意精度限制内。因此,例如,如果我想要一个 35.71428 的周期,我可以让一个 36 毫秒的计时器在它关闭 5 毫秒之前经过 15 次,然后终止它并启动一个新的。

【问题讨论】:

您需要一个实时操作系统!这在(大量)磁盘活动下如何保持? @Henk:它在磁盘活动繁重的情况下会卡顿,但在我的计算机上播放任何东西(视频、音乐等)也是如此。 【参考方案1】:

鉴于 .NET 的限制,我认为您有一个很好的方法。您的流程优先级是什么?我认为它需要高于正常水平以避免其他进程进行磁盘活动。

我同意 Henk 的观点,即 .NET 框架不是这里的最佳解决方案。如果发生垃圾回收,可能需要一段时间来释放对象、压缩堆等。

您使用的是什么操作系统?我也同意 Henk 的观点,即实时操作系统是最好的解决方案。根据我所阅读的内容,Windows CE 符合实时操作系统的要求,但我无法进一步评论。

【讨论】:

你的回答让我思考。我正在设置计时器线程的优先级,但我没有对我的进程的优先级做任何事情。在 .Net 中,您可以设置当前进程'PriorityClass,但是当我将其设置为 AboveNormalHighRealtime 时,它使我的问题变得更糟 - all 计时器回调最终会延迟 50-100 毫秒。至少可以说是违反直觉的。幸运的是,Process 类还有一个PriorityBoostEnabled 属性,当主窗口获得焦点时,它会临时提高进程的优先级。将此设置为 true 似乎可以解决我的问题。 我对优先级的唯一猜测是,可能提高您自己进程的优先级会将其置于计时器机制本身的优先级之上,这意味着您自己的应用程序正在警觉地等待现在延迟很多的计时器事件. 我想我说得太早了——问题仍然存在。有时问题会消失一段时间,这让我觉得我已经解决了,而我还没有解决。 这很有趣。您可能希望了解 .NET 执行 GC 时应用程序的行为。您可以使用垃圾收集通知msdn.microsoft.com/en-us/library/cc713687.aspx 对此过程进行一些控制。您还可以使用GC.AddMemoryPressure() 模拟一些内存压力。 我认为你是对的,我会尝试看看 GC 对此有何影响。我现在明白为什么提高进程优先级不起作用 - 较低优先级的计时器进程不允许“打扰”我的高优先级应用程序进程,因此回调会延迟。我想我实际上需要提高计时器的优先级。【参考方案2】:

计时器并不便宜,并且通过使用 1 毫秒计时器来接近 35.7 计时器,您在这里会吃掉自己的利润。我认为您可能处于 PC 本身所能达到的极限。 我会选择更长时间的计时器(35 或 36)并使用秒表计时器进行校正。

为了引发一些争议,我不太确定 .NET 框架是最适合此类任务的平台。您是否想过 GC 如何满足您的需求?

【讨论】:

@Henk:我可能误解了这一点,但我使用了大约 35 到 36 毫秒的更长周期(一次性)计时器。 1 ms 只是计时器的分辨率,而不是它的周期。 此外,.NET 框架在这种情况下的主要优势是它是应用程序已经编写的语言。我们称之为“在职优势”。 :)

以上是关于MFC timeSetEvent() 定时器的简单使用*的主要内容,如果未能解决你的问题,请参考以下文章

没有窗口怎么使用定时器

没有窗口怎么使用定时器

用于秒表实现的 MFC 时间工具

Delphi的DLL里如何实现定时器功能?

MFC中如何建立和结束一个线程

timesetevent与timekillevent的用法