有时候,人们会发现这样一个怪异现象:如果在执行一项长时间运行的任务的时候,在标题栏按下鼠标,这个时候,会发现程序运行的更快了一些。
这件奇怪的事情通常是发生在这样一种场景:当程序花费太多时间更新其进度状态而没有足够的时间用来做实际的计算工作。
(换句话说,程序员搞砸了。)
当你在标题栏上单击并按住鼠标时,窗口管理器会等待下一条鼠标消息,以便它可以确定你是单击标题还是尝试拖动。在等待期间,窗口的绘制工作将暂时停止。
这就是为什么程序运行得更快的原因:没有窗口绘画意味着花在更新上的 CPU 会减少,而无论如何更新都比读取更快。让我们通过一个简单的例子程序来演示这个现象。
>> 请移步至 topomel.com 以查看图片 <<
这个程序启动了一个后台线程,它计数到 100000,并且每次数值改变时都会使前台窗口无效。运行它,看数字加到 100000会有多快。(当循环结束时,我添加了一个小声音提示,所以你可以通过听声音提示来判断时间。)
现在再次运行它,但这一次,单击并按住标题栏上的鼠标。请注意,程序几乎立即发出声音提示:当你按住鼠标时,它运行得更快。这是因为所有绘画都被单击并按住标题时触发的可能拖动操作正在进行中抑制。
每次增加就更新屏幕显然是无意义的,因为增加得速度远远快于屏幕刷新速度,更不用说人眼读取速度。根据经验,每秒改变进度状态超过 10 次通常是无意义的。你在屏幕更新上付出的努力都白白浪费了。
让我们修改一下示例程序,使其每秒最多更新十次。我们将以 100ms 为间隔运行计时器,检查是否有任何更改,并重新绘制屏幕。
>> 请移步至 topomel.com 以查看图片 <<
我们不是在每次计数器改变值时就更新屏幕,而是仅仅设置一个”嘿,有些东西改变了”的标志,并在计时器上检查它。我们在生产者线程中使用释放语义来设置标志(因为我们希望在交换发生之前完成所有挂起的存储操作)并使用获取语义在消费者线程中清除标志(因为我们不希望将来任何的存储操作都被推测在交换之前)。
再次运行该程序,注意它瞬间计数直到 100000。当然,这并不能真正演示进度计数器,因此将 Sleep(1) 插入循环中:
>> 请移步至 topomel.com 以查看图片 <<
这足以使循环的速度变慢,因而可以看见递增的值。并不是像在最初版本中看见的令人眼花缭乱的递增,但是足以使人们领会其含义。
我用于在后台和前台线程之间传递信息的机制假定后台更新相对频繁,这样计时器几乎总会发现一些值得做的事情。如果混合执行一些快速和慢速任务,可以修改通信机制,这样当注意到一段时间没有更新时,计时器将自行关闭。后台线程恢复更新值时需要重新启动计时器。我没有费心编写这种更复杂的版本,因为这只会分散文章的要点。
总结
我曾经也想过将程序的每次状态变更都呈现在用户界面上,觉得这可以让用户了解程序的实时运行状态。但是在后来的实际体验中,我感觉这并不是想象中那样美好。
从用户的角度来说,他/她所希望的事情是:赶紧帮我把活儿做完,别成天整这些花里胡哨的东西。
确实如此!毕竟大家都这么忙。
最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《Why does my program run faster if I click and hold the caption bar?》