{"id":105659,"date":"2021-09-07T07:00:00","date_gmt":"2021-09-07T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105659"},"modified":"2021-09-07T06:36:55","modified_gmt":"2021-09-07T13:36:55","slug":"20210907-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210907-00\/?p=105659","title":{"rendered":"Ordering asynchronous updates with coroutines, part 2: Restart with hand-off"},"content":{"rendered":"<p>Another serialization pattern for coroutines is where calling some method initiates some asynchronous activity, and if the method gets called again while the activity is still incomplete, you want to let the previous activity run to completion, and then run it again.<\/p>\n<p>For example, maybe you have a method called <code>SetColor<\/code> that changes the color in private state and asynchronously propagates that color into another component. If the previous color-setting operation is still in progress when a second <code>SetColor<\/code> occurs, you want to let the propagation of the old color finish, and once that&#8217;s done, start pushing out the new color. (Intermediate colors are not important; only the last color counts.)<\/p>\n<pre>std::mutex m_mutex;\r\nbool m_busy = false;\r\n\r\nwinrt::fire_and_forget Widget::SetColor(Color newColor)\r\n{\r\n    auto lock = std::unique_lock(m_mutex);\r\n    m_color = newColor;\r\n    if (std::exchange(m_busy, true)) {\r\n        co_return;\r\n    }\r\n\r\n    auto lifetime = get_strong();\r\n\r\n    Color latestColor;\r\n    do {\r\n        latestColor = m_color;\r\n        lock.unlock();\r\n        try {\r\n            co_await UpdateColorOfExternalPartner(latestColor);\r\n        } catch (...) {\r\n            \/\/ nowhere to report the error\r\n            \/\/ you can choose to log it or to fail fast\r\n        }\r\n        lock.lock();\r\n    } while (m_color != latestColor);\r\n    m_busy = false;\r\n}\r\n<\/pre>\n<p>The idea here is that after setting the private <code>m_color<\/code>, we check whether somebody else is already busy updating the color of the external partner. If so, then we just return immediately, knowing that the existing worker will pick up the new color eventually.<\/p>\n<p>If nobody is doing the work (the previous value of <code>m_busy<\/code> was <code>false<\/code>), then we assume responsibility for the work: We capture the most recently set color, and then drop the lock while we update the external partner. Once that&#8217;s done, we reacquire the lock and see if the color changed again in the meantime. If so, we go back and push the new latest color, repeating until we make it through an entire update cycle with the updated color equal to the current color.<\/p>\n<p>Now, this pattern assumes that we can detect that new work is needed by inspecting the <code>m_color<\/code>. But that may not always be the case, in which case we need a separate flag to say &#8220;New work was requested.&#8221;<\/p>\n<pre>std::mutex m_mutex;\r\nbool m_busy = false;\r\nbool m_refreshNeeded = false;\r\n\r\nwinrt::fire_and_forget Widget::Refresh()\r\n{\r\n    auto lock = std::unique_lock(m_mutex);\r\n    <span style=\"color: blue;\">m_refreshNeeded = true;<\/span>\r\n    if (std::exchange(m_busy, true)) {\r\n        co_return;\r\n    }\r\n    auto lifetime = get_strong();\r\n\r\n    <span style=\"color: blue;\">while (std::exchange(m_refreshNeeded, false))<\/span> {\r\n        lock.unlock();\r\n        try {\r\n            co_await RefreshExternalPartner();\r\n        } catch (...) {\r\n            \/\/ nowhere to report the error\r\n            \/\/ you can choose to log it or to fail fast\r\n        }\r\n        lock.lock();\r\n    }\r\n}\r\n<\/pre>\n<p>Since we don&#8217;t have a <code>m_color<\/code> to tell us that we need to do more work, we create an explicit <code>m_refreshNeeded<\/code> flag, and we use a <code>while<\/code> loop to keep refreshing the external partner until we manage to make it all the way to the end without another refresh request coming in.<\/p>\n<p>In the case where the object has thread affinity (common for UI objects), you may already have the requirement that <code>SetColor<\/code> or <code>Refresh<\/code> be called from the UI thread. More generally, if you can arrange that all accesses to <code>m_color<\/code>, <code>m_busy<\/code>, and <code>m_refreshNeeded<\/code> are on the same thread, then you don&#8217;t need the mutex at all, and all the uses of the <code>lock<\/code> object can be removed.\u00b9<\/p>\n<p>In these examples, I made the coroutines <code>fire_<wbr \/>and_<wbr \/>forget<\/code>, so they return to their callers quickly. If we changed them to return <code>IAsyncAction<\/code>, then the caller could <code>co_await<\/code> the call to wait for the update to complete. However, the way we structured the work, it means that if the object is constantly being updated, the first call to <code>Set\u00adColor<\/code> or <code>Refresh<\/code> ends up doing all of the work, and the <code>IAsyncAction<\/code> doesn&#8217;t complete until the final refresh, which is unfair to the first caller:<\/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 Refresh();<\/code><br \/>\n<code>\u00a0\u00a0m_busy = true;<\/code><br \/>\n<code>\u00a0\u00a0co_await RefreshExternalPartner();<\/code><\/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: solid 1px black;\">Caller 2<br \/>\n<code>co_await Refresh();<\/code><br \/>\n<code>\u00a0\u00a0m_needRefresh = true;<\/code><br \/>\n<code>\u00a0\u00a0co_return;<\/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>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid;\"><code>\u00a0\u00a0m_needRefresh = false;<\/code><br \/>\n<code>\u00a0\u00a0co_await RefreshExternalPartner();<\/code><\/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: solid 1px black;\">Caller 3<br \/>\n<code>co_await Refresh();<\/code><br \/>\n<code>\u00a0\u00a0m_needRefresh = true;<\/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_needRefresh = false;<\/code><br \/>\n<code>\u00a0\u00a0co_await RefreshExternalPartner();<\/code><br \/>\n<code>\u00a0\u00a0m_busy = false;<\/code><br \/>\n<code>\u00a0\u00a0co_return;<\/code><\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>We&#8217;ll come back to this issue after we look at some other patterns for serializing asynchronous operations.<\/p>\n<p><b>Exercise<\/b>: In the asynchronous refresh pattern, why use a <code>while<\/code> loop? Why couldn&#8217;t we have used the previous pattern of using a <code>do...while<\/code> loop, like this?<\/p>\n<pre>std::mutex m_mutex;\r\nbool m_busy = false;\r\nbool m_refreshNeeded = false;\r\n\r\nwinrt::fire_and_forget Widget::Refresh()\r\n{\r\n    auto lock = std::unique_lock(m_mutex);\r\n    if (std::exchange(m_busy, true)) {\r\n        <span style=\"color: blue;\">m_refreshNeeded = true;<\/span>\r\n        co_return;\r\n    }\r\n    auto cleanup = wil::scope_exit([&amp;] { m_busy = false; });\r\n    auto lifetime = get_strong();\r\n\r\n    <span style=\"color: blue;\">do<\/span> {\r\n        lock.unlock();\r\n        co_await RefreshExternalPartner();\r\n        lock.lock();\r\n    } <span style=\"color: blue;\">while (std::exchange(m_refreshNeeded, false))<\/span>;\r\n}\r\n<\/pre>\n<p><b>Bonus chatter<\/b>: You can be extra-clever and combine <code>m_busy<\/code> and <code>m_refreshNeeded<\/code> into a single atomic variable.<\/p>\n<pre>\/\/ 0 = not busy\r\n\/\/ 1 = busy\r\n\/\/ 2 = refresh needed\r\n\r\nstd::atomic&lt;int&gt; m_busy;\r\n\r\nwinrt::fire_and_forget Widget::Refresh()\r\n{\r\n    if (m_busy.exchange(2, std::memory_order_release)) {\r\n        co_return;\r\n    }\r\n    while (m_busy.fetch_sub(1, std::memory_order_acquire) == 2) {\r\n        ... do work ...\r\n    }\r\n}\r\n<\/pre>\n<p>The initial exchange publishes the request to refresh the external partner, so it uses release semantics. If the previous value is nonzero, then it means that somebody else is already working, so we can return immediately and let the existing worker pick up the refresh request.<\/p>\n<p>If nobody is doing work, then we have to do it. We decrement the busy count and see if there is work to do. If so, the busy count is decremented to 1, meaning &#8220;We are doing work, and no additional work has been requested.&#8221; After doing the work, we loop back and decrement again. If work has been requested in the meantime, the value will have been bumped up to 2, so our decrement drops it back to 1, and the loop continues. Eventually, we make it all the way through without anybody requesting more work, which we detect when the busy count decrements all the way to zero.<\/p>\n<p>\u00b9 Note that we are assuming that <code>Update\u00adColor\u00adOf\u00adExternal\u00adPartner<\/code> and <code>Refresh\u00adExternal\u00adPartner<\/code> return <code>IAsyncAction<\/code> or otherwise ensure that the <code>co_await<\/code> resumes in the same COM context in which it suspended.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Everybody just waits their turn.<\/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-105659","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Everybody just waits their turn.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105659","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=105659"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105659\/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=105659"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105659"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105659"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}