{"id":112365,"date":"2026-05-28T07:00:00","date_gmt":"2026-05-28T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112365"},"modified":"2026-05-28T15:31:01","modified_gmt":"2026-05-28T22:31:01","slug":"20260528-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260528-00\/?p=112365","title":{"rendered":"Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 2"},"content":{"rendered":"<p>Last time, <a title=\"Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 1\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260527-00\/?p=112361\"> we tried to write a coroutine function that cached the result of another coroutine<\/a>, but our first attempt had lots of problems.<\/p>\n<p>It turns out that you can do it much more simply, and simpler code means fewer places you can mess up.<\/p>\n<pre>struct Widget : WidgetT&lt;Widget&gt;\r\n{\r\n    std::optional&lt;winrt::Thing&gt; m_thing;\r\n    wil::unique_event m_busy{ wil::EventOptions::Signaled }; \/\/ auto-reset, initially signaled\r\n\r\n    IAsyncOperation&lt;winrt::Thing&gt; GetThingAsync()\r\n    {\r\n        auto lifetime = get_strong();\r\n\r\n        co_await winrt::resume_on_signal(m_busy.get());\r\n        auto not_busy = m_busy.SetEvent_scope_exit();\r\n\r\n        \/\/ If we don't have a thing, try to get one.\r\n        if (!m_thing) {\r\n            m_thing = co_await GetThingWorkerAsync();\r\n        }\r\n\r\n        co_return *m_thing;\r\n    }\r\n};\r\n<\/pre>\n<p>We use an auto-reset event to serialize access to the function, remembering to set the event when control leaves the function so that the next caller can try.<\/p>\n<p>Each time we try, we see if we have an answer already. If not, then we try to get the answer. If it fails, then we propagate the exception and <code>m_thing<\/code> remains empty. Otherwise, we save the answer into <code>m_thing<\/code>. Regardless of whether we have a cached answer or a fresh answer, we return it. (We can use the <code>*<\/code> operator because we know that the <code>m_thing<\/code> contains a value: If it didn&#8217;t, we would have attempted to get the value, and if the attempt failed, we would have thrown.)<\/p>\n<p>The above code is careful to accommodate the case that <code>Get\u00adThing\u00adWorker\u00adAsync<\/code> succeeds and produces <code>nullptr<\/code>, using the <code>std::<wbr \/>optional<\/code>&#8216;s empty state as a &#8220;no value yet&#8221; sentinel. If you know that <code>Get\u00adThing\u00adWorker\u00adAsync<\/code> cannot succeed with <code>nullptr<\/code>, then you can get rid of the <code>std::<wbr \/>optional<\/code> and let <code>nullptr<\/code> represent the empty state.<\/p>\n<pre>struct Widget : WidgetT&lt;Widget&gt;\r\n{\r\n    <span style=\"border: solid 1px currentcolor;\">winrt::Thing m_thing{ nullptr };<\/span>\r\n    wil::unique_event m_busy{ wil::EventOptions::Signaled }; \/\/ auto-reset, initially signaled\r\n\r\n    IAsyncOperation&lt;winrt::Thing&gt; GetThingAsync()\r\n    {\r\n        auto lifetime = get_strong();\r\n\r\n        co_await winrt::resume_on_signal(m_busy.get());\r\n        auto not_busy = m_busy.SetEvent_scope_exit();\r\n\r\n        \/\/ If we don't have a thing, try to get one.\r\n        if (!m_thing) {\r\n            m_thing = co_await GetThingWorkerAsync();\r\n            <span style=\"border: solid 1px currentcolor;\">assert(m_thing);<\/span>\r\n        }\r\n\r\n        co_return <span style=\"border: solid 1px currentcolor;\">m_thing<\/span>;\r\n    }\r\n};\r\n<\/pre>\n<p>Next time, we&#8217;ll come up with a version that tries only once rather than trying until it succeeds.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Just let each person take turns trying.<\/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-112365","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Just let each person take turns trying.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112365","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=112365"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112365\/revisions"}],"predecessor-version":[{"id":112366,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112365\/revisions\/112366"}],"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=112365"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112365"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112365"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}