{"id":109697,"date":"2024-04-23T07:00:00","date_gmt":"2024-04-23T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109697"},"modified":"2024-04-23T09:39:02","modified_gmt":"2024-04-23T16:39:02","slug":"20240423-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240423-00\/?p=109697","title":{"rendered":"Adding state to the update notification pattern, part 5"},"content":{"rendered":"<p>We&#8217;ve been looking at the problem of <a title=\"Adding state to the update notification pattern, part 1\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240417-00\/?p=109679\"> a stateful but coalescing update notification<\/a>, where multiple requests for work can arrive, and your only requirement is that you send a notification for the last one.<\/p>\n<p>This time, we&#8217;ll apply the trick of <a title=\"Lock-free algorithms: The try\/commit\/(try again) pattern\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20110412-00\/?p=10963\"> using a counter to record who is doing the work on behalf of the most recent change<\/a>. Here&#8217;s our first attempt:<\/p>\n<pre>class EditControl\r\n{\r\n    \u27e6 ... existing class members ... \u27e7\r\n\r\n    <span style=\"border: solid 1px currentcolor;\">unsigned m_latestId;<\/span>\r\n};\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    co_await winrt::resume_background();\r\n\r\n    <span style=\"border: solid 1px currentcolor;\">auto id = ++m_latestId;<\/span>\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    }\r\n\r\n    co_await winrt::resume_foreground(Dispatcher());\r\n\r\n    <span style=\"border: solid 1px currentcolor;\">if (id != m_latestId) co_return;<\/span>\r\n\r\n    SetAutocomplete(matches);\r\n}\r\n<\/pre>\n<p>Just before we set the results, we check if the <code>m_latestId<\/code> has changed since when we started. If so, then our calculations are stale, and we abandon the operation. Otherwise, we proceed with the results. And we use an unsigned integer for the change counter to avoid undefined behavior on signed overflow.<\/p>\n<p>There is still a problem here: We hop to a background thread <i>before<\/i> increment the counter. This means that the counter is not necessarily assigned sequentially to each call to <code>TextChanged<\/code>. Suppose the <code>m_latestId<\/code> is initially 0.<\/p>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<th style=\"border: solid 1px currentcolor; border-top: none; padding: 0 1ex;\">UI thread<\/th>\n<th style=\"border: solid 1px currentcolor; border-top: none; padding: 0 1ex;\">Background thread 1<\/th>\n<th style=\"border: solid 1px currentcolor; border-top: none; padding: 0 1ex;\">Background thread 2<\/th>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\"><tt>TextChanged(\"Bob\")<\/tt><br \/>\n<tt>resume_background()<\/tt><\/td>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">\u00a0<\/td>\n<td style=\"padding: 0 1ex;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">\u00a0<\/td>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">(Bob&#8217;s task)<\/td>\n<td style=\"padding: 0 1ex;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\"><tt>TextChanged(\"Alice\");<\/tt><br \/>\n<tt>resume_background()<\/tt><\/td>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">\u00a0<\/td>\n<td style=\"padding: 0 1ex;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">\u00a0<\/td>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">\u00a0<\/td>\n<td style=\"padding: 0 1ex;\">(Alice&#8217;s task)<br \/>\n<tt>id = m_latestId;<\/tt> (id is 1)<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">\u00a0<\/td>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\"><tt>id = m_latestId;<\/tt> (id is 2)<br \/>\ncalculate matches for &#8220;Bob&#8221;<br \/>\n<tt>resume_foreground()<\/tt><\/td>\n<td style=\"padding: 0 1ex;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">(Bob&#8217;s task)<br \/>\n<tt>if (2 == m_latestId) (true)<\/tt><br \/>\n<tt>SetAutocomplete(bob's matches)<\/tt><br \/>\n(Bob&#8217;s task completes)<\/td>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">\u00a0<\/td>\n<td style=\"padding: 0 1ex;\">calculate matches for &#8220;Alice&#8221;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">\u00a0<\/td>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">\u00a0<\/td>\n<td style=\"padding: 0 1ex;\"><tt>resume_foreground()<\/tt><\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">(Alice&#8217;s task)<br \/>\n<tt>if (1 == m_latestId) (false)<\/tt><br \/>\n(Alice&#8217;s task completes)<\/td>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">\u00a0<\/td>\n<td style=\"padding: 0 1ex;\">\u00a0<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>We incremented the change counter <i>after<\/i> hopping to the background thread, which means that it happens after we have lost control over the ordering of the work. We have to update the change counter from the UI thread so that the counter values correspond to the order in which the calls arrived, not the order in which the work began.<\/p>\n<p>The result of this fix is this:<\/p>\n<pre>winrt::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;\">auto id = ++m_latestId;<\/span>\r\n\r\n    co_await winrt::resume_background();\r\n\r\n    std::vector&lt;std::string&gt; matches = FindMatches(text);\r\n\r\n    co_await winrt::resume_foreground(Dispatcher());\r\n\r\n    if (id != m_latestId) co_return;\r\n\r\n    SetAutocomplete(matches);\r\n}\r\n<\/pre>\n<p>This approach works, but it is a bit wasteful: If there are multiple changes in rapid succession, we do the work of finding matches for every text change, and throw away all but the last one.<\/p>\n<p>Next time, we&#8217;ll work toward making this more efficient.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Using a change counter.<\/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-109697","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Using a change counter.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109697","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=109697"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109697\/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=109697"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109697"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109697"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}