{"id":105666,"date":"2021-09-09T07:00:00","date_gmt":"2021-09-09T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105666"},"modified":"2021-09-09T06:23:45","modified_gmt":"2021-09-09T13:23:45","slug":"20210909-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210909-00\/?p=105666","title":{"rendered":"Ordering asynchronous updates with coroutines, part 4: Bowing out, explicit version"},"content":{"rendered":"<p>Last time, we looked at the &#8220;Everybody tries, but only one wins&#8221; pattern, in which everyone calculates a result, but only the last one gets to save it. While this does work, we noted that there&#8217;s an inefficiency: Every calculation runs to completion, even if it has been superseded.<\/p>\n<p>We can address that problem by re-checking after every coroutine resumption whether we have already lost. If so, we just give up.<\/p>\n<pre><span style=\"color: blue;\">bool Widget::KeepGoingAfterAwait(uint32_t counter)\r\n{\r\n    std::lock_guard guard{ m_mutex };\r\n    return counter = m_counter;\r\n}<\/span>\r\n\r\nwinrt::IAsyncAction Widget::RecalcAsync()\r\n{\r\n    auto lifetime = get_strong();\r\n\r\n    uint32_t counter;\r\n    winrt::hstring messageId;\r\n    winrt::hstring lang;\r\n    {\r\n        std::lock_guard guard{ m_mutex };\r\n        counter = ++m_counter;\r\n        messageId = m_messageId;\r\n        lang = m_lang;\r\n    }\r\n\r\n    auto resolved = co_await ResolveLanguageAsync(lang);\r\n    <span style=\"color: blue;\">if (!KeepGoingAfterAwait(counter)) co_return;<\/span>\r\n    auto library = co_await GetResourceLibraryAsync(resolved);\r\n    <span style=\"color: blue;\">if (!KeepGoingAfterAwait(counter)) co_return;<\/span>\r\n    auto message = library.LookupResourceAsync(messageId);\r\n    <span style=\"color: blue;\">if (!KeepGoingAfterAwait(counter)) co_return;<\/span>\r\n\r\n    std::lock_guard guard{ m_mutex };\r\n    if (m_counter == counter) {\r\n        m_message = message;\r\n    }\r\n}\r\n<\/pre>\n<p>After every <code>co_await<\/code>, we check whether our counter is still current. If not, then it means that while we were <code>co_await<\/code>ing, somebody else started a <code>Refresh\u00adAsync<\/code> which caused our refresh to become obsolete. Instead of proceeding with the work, only to reject it at the end, we just stop immediately.<\/p>\n<p>The last <code>Keep\u00adGoing\u00adAfter\u00adAwait()<\/code> check is redundant because we&#8217;re going to check one last time inside the lock, but I wrote it out anyway.<\/p>\n<p>As we observed earlier, we can get rid of the locks if all accesses to the members are on a single thread.<\/p>\n<pre>bool Widget::KeepGoingAfterAwait(uint32_t counter)\r\n{\r\n    return counter = m_counter;\r\n}\r\n\r\nwinrt::IAsyncAction Widget::RecalcAsync()\r\n{\r\n    auto lifetime = get_strong();\r\n\r\n    uint32_t counter;\r\n    winrt::hstring messageId;\r\n    winrt::hstring lang;\r\n    {\r\n        std::lock_guard guard{ m_mutex };\r\n        counter = ++m_counter;\r\n        messageId = m_messageId;\r\n        lang = m_lang;\r\n    }\r\n\r\n    auto resolved = co_await ResolveLanguageAsync(lang);\r\n    if (!KeepGoingAfterAwait(counter)) co_return;\r\n    auto library = co_await GetResourceLibraryAsync(resolved);\r\n    if (!KeepGoingAfterAwait(counter)) co_return;\r\n    auto message = library.LookupResourceAsync(messageId);\r\n    if (!KeepGoingAfterAwait(counter)) co_return;\r\n\r\n    m_message = message;\r\n}\r\n<\/pre>\n<p>I removed the final check of the <code>m_counter<\/code>, since it is redundant with the <code>Keep\u00adGoing\u00adAfter\u00adAwait()<\/code> that immediately precedes it.<\/p>\n<p>Next time, we&#8217;ll see how this pattern is already covered by existing functionality.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Realizing you&#8217;re not the one.<\/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-105666","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Realizing you&#8217;re not the one.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105666","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=105666"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105666\/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=105666"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105666"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105666"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}