{"id":105662,"date":"2021-09-08T07:00:00","date_gmt":"2021-09-08T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105662"},"modified":"2021-09-08T06:24:45","modified_gmt":"2021-09-08T13:24:45","slug":"20210908-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210908-00\/?p=105662","title":{"rendered":"Ordering asynchronous updates with coroutines, part 3: Let them all compete, but only one wins"},"content":{"rendered":"<p>Previously, we looked at the case where calling a method initiates some asynchronous activity, and if new activity is required, the work is handed off to the existing coroutine. A different model is to have everybody do the work in parallel, but only the last one counts. Of course, this pattern assumes that the work can safely be performed in parallel, such as perform a complex calculation.<\/p>\n<p>Let&#8217;s assume that the object has thread affinity, so we can assume that all accesses on the UI thread are uncontended and therefore do not require a lock.<\/p>\n<pre>std::mutex m_mutex;\r\nwinrt::hstring m_lang;\r\nint32_t m_messageId;\r\nwinrt::hstring m_message;\r\n\r\nwinrt::IAsyncAction Widget::SetMessageAsync(int32_t messageId)\r\n{\r\n    auto lifetime = get_strong();\r\n    {\r\n        std::lock_guard guard{ m_mutex };\r\n        m_messageId = messageId;\r\n    }\r\n    co_await RecalcAsync();\r\n}\r\n\r\nwinrt::IAsyncAction Widget::SetLanguageAsync(winrt::hstring lang)\r\n{\r\n    auto lifetime = get_strong();\r\n    {\r\n        std::lock_guard guard{ m_mutex };\r\n        m_lang = lang;\r\n    }\r\n    co_await RecalcAsync();\r\n}\r\n\r\nwinrt::IAsyncAction Widget::RecalcAsync()\r\n{\r\n    auto lifetime = get_strong();\r\n\r\n    winrt::hstring messageId;\r\n    winrt::hstring lang;\r\n    {\r\n        std::lock_guard guard{ m_mutex };\r\n        messageId = m_messageId;\r\n        lang = m_lang;\r\n    }\r\n\r\n    auto resolved = co_await ResolveLanguageAsync(lang);\r\n    auto library = co_await GetResourceLibraryAsync(resolved);\r\n    auto message = co_await library.LookupResourceAsync(messageId);\r\n\r\n    std::lock_guard guard{ m_mutex };\r\n    if (m_messageId == messageId &amp;&amp; m_lang == lang) {\r\n        m_message = message;\r\n    }\r\n}\r\n<\/pre>\n<p>The pattern here is that on each call to <code>Set\u00adMessage\u00adId()<\/code> or <code>Set\u00adLanguage()<\/code>, we update our local state variables and then call a common helper coroutine to recalculate the <code>m_message<\/code>.<\/p>\n<p>The pattern in <code>Recalc\u00adAsync<\/code> goes like this:<\/p>\n<ul>\n<li>Capture the member variables you need into local variables.<\/li>\n<li>Do the asynchronous work, operating purely on the local variables.<\/li>\n<li>When finished, compare the member variables against the local variables to see if they still match.<\/li>\n<li>If so, then update the results.<\/li>\n<li>If not, then somebody else changed the <code>m_messageId<\/code> or <code>m_lang<\/code> in the meantime, so abandon the update.<\/li>\n<\/ul>\n<p>In the case where the update is abandoned, we don&#8217;t have to restart the calculation because whoever changed the message ID or the language is running their own <code>Recalc\u00adAsync<\/code>. In fact, <i>everyone<\/i> who changes the message ID or the language is running their own <code>Recalc\u00adAsync<\/code>, and only the one whose calculations match the current state gets to update the result. Note that this may not be the calculation that finishes last.<\/p>\n<p>The &#8220;capture and compare&#8221; pattern assumes that the calculations are idempotent. If not, then you can use a counter to keep track of who is running the &#8220;real&#8221; computation.<\/p>\n<pre><span style=\"color: blue;\">uint32_t m_counter = 0;<\/span>\r\n\r\nwinrt::IAsyncAction Widget::RecalcAsync()\r\n{\r\n    auto lifetime = get_strong();\r\n\r\n    <span style=\"color: blue;\">uint32_t counter;<\/span>\r\n    winrt::hstring messageId;\r\n    winrt::hstring lang;\r\n    {\r\n        std::lock_guard guard{ m_mutex };\r\n        <span style=\"color: blue;\">counter = ++m_counter;<\/span>\r\n        messageId = m_messageId;\r\n        lang = m_lang;\r\n    }\r\n\r\n    auto resolved = co_await ResolveLanguageAsync(lang);\r\n    auto library = co_await GetResourceLibraryAsync(resolved);\r\n    auto message = co_await library.LookupResourceAsync(messageId);\r\n\r\n    std::lock_guard guard{ m_mutex };\r\n    if (<span style=\"color: blue;\">m_counter == counter<\/span>) {\r\n        m_message = message;\r\n    }\r\n}\r\n<\/pre>\n<p>We use a counter to keep track of which instance of the recalculation we are managing, and when we finish our calculations, we check if the counter has changed since we started. If not, then we are the active recalculation and can update the result. If the counter doesn&#8217;t match, then somebody else triggered a recalculation while we were recalculating, and we&#8217;ll let that other recalculation set the result.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td style=\"border: 1px black; border-style: solid solid none solid;\">Caller 1<br \/>\n<code>co_await RecalcAsync();<\/code><br \/>\n<code>\u00a0\u00a0counter = m_counter = 1;<\/code><br \/>\n<code>\u00a0\u00a0co_await ResolveLanguageAsync(...);<\/code><\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: solid solid none solid;\">Caller 2<br \/>\n<code>co_await RecalcAsync();<\/code><br \/>\n<code>\u00a0\u00a0counter = m_counter = 2;<\/code><br \/>\n<code>\u00a0\u00a0co_await ResolveLanguageAsync(...);<\/code><\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid;\"><code>\u00a0\u00a0co_await GetResourceLibraryAsync(...);<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: solid solid none solid;\">Caller 3<br \/>\n<code>co_await RecalcAsync();<\/code><br \/>\n<code>\u00a0\u00a0counter = m_counter = 3;<\/code><br \/>\n<code>\u00a0\u00a0co_await ResolveLanguageAsync(...);<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid;\"><code>\u00a0\u00a0co_await GetResourceLibraryAsync(...);<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid;\"><code>\u00a0\u00a0co_await GetResourceLibraryAsync(...);<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid;\"><code>\u00a0\u00a0co_await LookupResourceAsync(...);<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid;\"><code>\u00a0\u00a0co_await LookupResourceAsync(...);<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid;\"><code>\u00a0\u00a0co_await LookupResourceAsync(...);<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid solid solid;\"><code>\u00a0\u00a0m_counter is 3<\/code><br \/>\n<code>\u00a0\u00a0counter is 2<\/code><br \/>\n<code>\u00a0\u00a0do not update m_message<\/code><br \/>\n<code>\u00a0\u00a0co_return;<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid; background-color: #eeeeee;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid solid solid;\"><code>\u00a0\u00a0m_counter is 3<\/code><br \/>\n<code>\u00a0\u00a0counter is 3<\/code><br \/>\n<code>\u00a0\u00a0update m_message<\/code><br \/>\n<code>\u00a0\u00a0co_return;<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid solid solid;\"><code>\u00a0\u00a0m_counter is 3<\/code><br \/>\n<code>\u00a0\u00a0counter is 1<\/code><br \/>\n<code>\u00a0\u00a0do not update m_message<\/code><br \/>\n<code>\u00a0\u00a0co_return;<\/code><\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Everybody tries to recalculate, but only the one that performed the most recent <code>Recalc\u00adAsync<\/code> gets to update the result.<\/p>\n<p>You may recognize this as the coroutine version of the <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20110412-00\/?p=10963\"> lock-free try\/commit\/abandon pattern<\/a>.<\/p>\n<p>This pattern solves the fairness problem we saw last time: No instance of <code>Recalc\u00adAsync<\/code> is being asked to calculate more than once, so it&#8217;s not the case that repeated recalculation requests cause one instance to do an unbounded amount of work on behalf of others.<\/p>\n<p>This pattern does however result in a lot of wasted work. Once the second <code>Recalc\u00adAsync<\/code> begins, we all know that the work being done by the first call is pointless, since it will end up just throwing away the result. Next time, we&#8217;ll see what we can do to avoid that wasted work once we realize it&#8217;s going to be wasted.<\/p>\n<p><b>Bonus chatter<\/b>: In the case where the object is single-threaded, you can get rid of the locks, which makes the code much simpler. (This relies on the C++\/WinRT behavior that <code>co_await<\/code>&#8216;ing an <code>IAsyncAction<\/code> or <code>IAsyncOperation<\/code> resumes in the same COM context.)<\/p>\n<pre>uint32_t m_counter = 0;\r\n\r\nwinrt::IAsyncAction Widget::RecalcAsync()\r\n{\r\n    auto lifetime = get_strong();\r\n\r\n    auto counter = ++m_counter;\r\n    auto messageId = m_messageId;\r\n    auto lang = m_lang;\r\n\r\n    auto resolved = co_await ResolveLanguageAsync(lang);\r\n    auto library = co_await GetResourceLibraryAsync(resolved);\r\n    auto message = co_await library.LookupResourceAsync(messageId);\r\n\r\n    if (m_counter == counter) {\r\n        m_message = message;\r\n    }\r\n}\r\n<\/pre>\n<p>You can also hop to a background thread, as long as you hop back to the main thread when accessing the member variables.<\/p>\n<pre>uint32_t m_counter = 0;\r\n\r\nwinrt::IAsyncAction Widget::RecalcAsync()\r\n{\r\n    auto lifetime = get_strong();\r\n\r\n    auto counter = ++m_counter;\r\n    auto messageId = m_messageId;\r\n    auto lang = m_lang;\r\n\r\n    <span style=\"color: blue;\">co_await winrt::resume_background();<\/span>\r\n\r\n    auto resolved = co_await ResolveLanguageAsync(lang);\r\n    auto library = co_await GetResourceLibraryAsync(resolved);\r\n    auto message = co_await library.LookupResourceAsync(messageId);\r\n\r\n    <span style=\"color: blue;\">co_await winrt::resume_foreground(Dispatcher());<\/span>\r\n\r\n    if (m_counter == counter) {\r\n        m_message = message;\r\n    }\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Everybody tries, but only one wins.<\/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-105662","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Everybody tries, but only one wins.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105662","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=105662"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105662\/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=105662"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105662"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105662"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}