{"id":107607,"date":"2022-12-21T07:00:00","date_gmt":"2022-12-21T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107607"},"modified":"2022-12-15T15:58:53","modified_gmt":"2022-12-15T23:58:53","slug":"20221221-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20221221-00\/?p=107607","title":{"rendered":"Running some UI code on a timer at a higher priority than your usual timer messages, or without coalescing"},"content":{"rendered":"<p>A customer wanted something similar to a Windows <code>WM_TIMER<\/code> timer, but they didn&#8217;t want the timer to be a low-priority message <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20191108-00\/?p=103080\"> generated on demand<\/a>, and they didn&#8217;t want the messages to be coalesced. Is this possible?<\/p>\n<p>You can do this by using some other kind of timer, but using window messages to synchronize with the UI thread. In other words, treat this as a work queue where the work is generated by a non-UI timer.<\/p>\n<p>A na\u00efve solution would be to post a custom message each time the timer elapses:<\/p>\n<pre>HWND g_hwnd;\r\nPTP_TIMER g_timer = nullptr;\r\n\r\nvoid CALLBACK TimerCallback(PTP_CALLBACK_INSTANCE instance,\r\n    void* context, PTP_TIMER timer)\r\n{\r\n    PostMessage(g_hwnd, WM_DOSOMETHING, 0, 0);\r\n}\r\n\r\n\/\/ Must be called from UI thread\r\nvoid StartTimer(\r\n    FILETIME dueTime, DWORD period, DWORD window)\r\n{\r\n    g_timer = CreateThreadpoolTimer(TimerCallback,\r\n        nullptr, nullptr);\r\n    SetThreadpoolTimer(\r\n        g_timer, &amp;dueTime, period, window);\r\n}\r\n\r\n\/\/ Must be called from UI thread\r\nvoid StopTimer()\r\n{\r\n    CloseThreadpoolTimer(g_timer);\r\n}\r\n\r\nLRESULT CALLBACK WndProc(HWND hwnd, UINT message,\r\n    WPARAM wParam, LPARAM lParam)\r\n{\r\n    switch (message)\r\n    {\r\n    ...\r\n    case WM_DOSOMETHING:\r\n        DoSomething();\r\n        break;\r\n    ...\r\n    }\r\n    return DefWindowProc(hwnd, message, wParam, lParam);\r\n}\r\n<\/pre>\n<p>It&#8217;s very simple: Each time the timer fires, we post a &#8220;do something&#8221; message to the window.<\/p>\n<p>The problem with this is that it can flood the message queue if the UI thread gets blocked for a long time for some reason. The timer keeps running, and new <code>WM_<wbr \/>DO\u00adSOMETHING<\/code> messages are posted into the queue, but the UI thread has stopped processing messages, so the queued messages just accumulate, and you risk overflowing the message queue.<\/p>\n<p>Better is to use an edge-triggered message, using a technique <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20161123-00\/?p=94766\"> we saw some time ago<\/a>.<\/p>\n<pre>HWND g_hwnd;\r\nPTP_TIMER g_timer = nullptr;\r\n\r\n\/\/ alternate: LONG g_count;\r\nstd::atomic&lt;int&gt; g_count;\r\n\r\nvoid CALLBACK TimerCallback(PTP_CALLBACK_INSTANCE instance,\r\n    void* context, PTP_TIMER timer)\r\n{\r\n    \/\/ alternate: InterlockedIncrement(&amp;g_count)\r\n    if (g_count.fetch_add(1,\r\n            std::memory_order_relaxed) == 1) {\r\n        PostMessage(g_hwnd, WM_DOSOMETHING, 0, 0);\r\n    }\r\n}\r\n\r\nLRESULT CALLBACK WndProc(HWND hwnd, UINT message,\r\n    WPARAM wParam, LPARAM lParam)\r\n{\r\n    switch (message)\r\n    {\r\n    ...\r\n\r\n    case WM_DOSOMETHING:\r\n        {\r\n            \/\/ alternate: InterlockedExchange(&amp;g_count, 0)\r\n            int count = g_count.exchange(0,\r\n                std::memory_order_relaxed);\r\n            for (int i = 0; i &lt; count; i++) DoSomething();\r\n        }\r\n        break;\r\n    ...\r\n    }\r\n    return DefWindowProc(hwnd, message, wParam, lParam);\r\n}\r\n<\/pre>\n<p>This time, when the timer expires, we just increment the number of outstanding operations. If the number incremented from zero to one, then we post a message to get the UI thread to drain the work.<\/p>\n<p>When the UI thread receives the message, it exchanges the counter back to zero and then operates on each of the operations that were outstanding. In this simple example, we just do the &#8220;something&#8221; that many times. In a real program, you would probably write a special version <code>DoSomethingN(count)<\/code> which is more efficient than doing <code>DoSomething()<\/code> <var>N<\/var> times.<\/p>\n<p>Note that I&#8217;ve been glossing over the problem of what to do if <code>StopTimer()<\/code> is called while there are still pending operations. As-written, what happens is that the pending operations will still be performed later. If you want them to be flushed or recalled, you have a little more work to do. I&#8217;ll leave you work out the details of threadpool timers, but the idea is<\/p>\n<ul>\n<li>Stop the timer and wait for callbacks to complete.<\/li>\n<li>Exchange the <code>g_count<\/code> back to zero.<\/li>\n<li>If flushing: Perform <code>DoSomething()<\/code> that many times immediately.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>You can build your own timer system.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-107607","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You can build your own timer system.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107607","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=107607"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107607\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=107607"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107607"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107607"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}