為什么單擊并按住標題欄,程序運行得更快?
有時候,人們會發現這樣一個怪異現象:如果在執行一項長時間運行的任務的時候,在標題欄按下鼠標,這個時候,會發現程序運行的更快了一些。
這件奇怪的事情通常是發生在這樣一種場景:當程序花費太多時間更新其進度狀態而沒有足夠的時間用來做實際的計算工作。(換句話說,程序員搞砸了。)
當你在標題欄上單擊并按住鼠標時,窗口管理器會等待下一條鼠標消息,以便它可以確定你是單擊標題還是嘗試拖動。在等待期間,窗口的繪制工作將暫時停止。這就是為什么程序運行得更快的原因:沒有窗口繪畫意味著花在更新上的 CPU 會減少,而無論如何更新都比讀取更快。讓我們通過一個簡單的例子程序來演示這個現象。
這個程序啟動了一個后臺線程,它計數到 100000,并且每次數值改變時都會使前臺窗口無效。運行它,看數字加到 100000會有多快。(當循環結束時,我添加了一個小聲音提示,所以你可以通過聽聲音提示來判斷時間。)
現在再次運行它,但這一次,單擊并按住標題欄上的鼠標。請注意,程序幾乎立即發出聲音提示:當你按住鼠標時,它運行得更快。這是因為所有繪畫都被單擊并按住標題時觸發的可能拖動操作正在進行中抑制。
每次增加就更新屏幕顯然是無意義的,因為增加得速度遠遠快于屏幕刷新速度,更不用說人眼讀取速度。根據經驗,每秒改變進度狀態超過 10 次通常是無意義的。你在屏幕更新上付出的努力都白白浪費了。
讓我們修改一下示例程序,使其每秒最多更新十次。我們將以 100ms 為間隔運行計時器,檢查是否有任何更改,并重新繪制屏幕。
我們不是在每次計數器改變值時就更新屏幕,而是僅僅設置一個”嘿,有些東西改變了”的標志,并在計時器上檢查它。我們在生產者線程中使用釋放語義來設置標志(因為我們希望在交換發生之前完成所有掛起的存儲操作)并使用獲取語義在消費者線程中清除標志(因為我們不希望將來任何的存儲操作都被推測在交換之前)。
再次運行該程序,注意它瞬間計數直到 100000。當然,這并不能真正演示進度計數器,因此將 Sleep(1) 插入循環中:
這足以使循環的速度變慢,因而可以看見遞增的值。并不是像在最初版本中看見的令人眼花繚亂的遞增,但是足以使人們領會其含義。
我用于在后臺和前臺線程之間傳遞信息的機制假定后臺更新相對頻繁,這樣計時器幾乎總會發現一些值得做的事情。如果混合執行一些快速和慢速任務,可以修改通信機制,這樣當注意到一段時間沒有更新時,計時器將自行關閉。后臺線程恢復更新值時需要重新啟動計時器。我沒有費心編寫這種更復雜的版本,因為這只會分散文章的要點。
總結
我曾經也想過將程序的每次狀態變更都呈現在用戶界面上,覺得這可以讓用戶了解程序的實時運行狀態。但是在后來的實際體驗中,我感覺這并不是想象中那樣美好。從用戶的角度來說,他/她所希望的事情是:趕緊幫我把活兒做完,別成天整這些花里胡哨的東西。
確實如此!畢竟大家都這么忙。
最后
Raymond Chen的《The Old New Thing》是我非常喜歡的博客之一,里面有很多關于Windows的小知識,對于廣大Windows平臺開發者來說,確實十分有幫助。本文來自:《Why does my program run faster if I click and hold the caption bar?》