{"id":109689,"date":"2024-04-19T07:00:00","date_gmt":"2024-04-19T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109689"},"modified":"2024-04-19T08:09:55","modified_gmt":"2024-04-19T15:09:55","slug":"20240419-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240419-00\/?p=109689","title":{"rendered":"Adding state to the update notification pattern, part 3"},"content":{"rendered":"<p>Last time, we developed <a title=\"Adding state to the update notification pattern, part 2\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240418-00\/?p=109685\"> a stateful but coalescing update notification<\/a>, and we noted that the code does a lot of unnecessary work because the worker thread calculates all the matches, even if the work has been superseded by another request.<\/p>\n<p>We can add an optimization to abandon the background work if it notices that its efforts are going to waste: Periodically check whether there is any pending text. This will cost us a mutex, however, to protect access to <code>m_pendingText<\/code> from multiple threads.<\/p>\n<pre>class EditControl\r\n{\r\n    \u27e6 ... existing class members ... \u27e7\r\n\r\n    bool m_busy = false;\r\n    <span style=\"border: solid 1px currentcolor;\">std::mutex m_mutex;<\/span>\r\n    std::optional&lt;string&gt; m_pendingText;\r\n};\r\nwinrt::fire_and_forget\r\nEditControl::TextChanged(std::string text)\r\n{\r\n    auto lifetime = get_strong();\r\n\r\n    <span style=\"border: solid 1px currentcolor;\">ExchangePendingText(std::move(text));<\/span>\r\n    if (std::exchange(m_busy, true)) {\r\n        co_return;\r\n    }\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">while (auto pendingText = ExchangePendingText(std::nullopt);<\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">       pendingText) {                                       <\/span>\r\n\r\n        co_await winrt::resume_background();\r\n\r\n        <span style=\"border: solid 1px currentcolor;\">auto matches = BuildMatches(*pendingText);<\/span>\r\n\r\n        co_await winrt::resume_foreground(Dispatcher());\r\n\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">if (matches) {                <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    SetAutocomplete(*matches);<\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">}                             <\/span>\r\n\r\n    }\r\n    m_busy = false;\r\n}\r\n\r\ntemplate&lt;typename T = std::optional&lt;std::string&gt;&gt;\r\nstd::optional&lt;std::string&gt;\r\n    EditControl::ExchangePendingText(T&amp;&amp; pending)\r\n{\r\n    auto lock = std::unique_lock(m_mutex);\r\n    return std::exchange(m_pendingText, std::forward&lt;T&gt;(pending));\r\n}\r\n\r\nstd::optional&lt;std::vector&lt;std::string&gt;&gt;\r\n    EditControl::BuildMatches(std::string const&amp; text)\r\n{\r\n    std::vector&lt;std::string&gt; matches;\r\n    for (auto&amp;&amp; candidate : FindCandidates(text)) {\r\n        if (candidate.Verify()) {\r\n            matches.push_back(candidate.Text());\r\n        }\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">if (auto lock = std::unique_lock(m_mutex);<\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    m_pendingText) {                      <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    return std::nullopt;                  <\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                         <\/span>\r\n    }\r\n    return matches;\r\n}\r\n<\/pre>\n<p>Last time, I noted that the UI thread is doing a lot of work for us, since it is implicitly ensuring that the updates to <code>m_busy<\/code> and <code>m_pendingText<\/code> are atomic.<\/p>\n<p>If our background work doesn&#8217;t dispatch back to the UI thread, then we will be responsible for our own locking. We&#8217;ll look at that next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Abandoning the background work if we know that it is pointless.<\/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-109689","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Abandoning the background work if we know that it is pointless.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109689","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=109689"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109689\/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=109689"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109689"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109689"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}