{"id":112368,"date":"2026-05-29T07:00:00","date_gmt":"2026-05-29T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112368"},"modified":"2026-05-29T20:24:33","modified_gmt":"2026-05-30T03:24:33","slug":"20260529-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260529-00\/?p=112368","title":{"rendered":"Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 3"},"content":{"rendered":"<p>Last time, <a title=\"Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 2\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260528-00\/?p=112365\"> we wrote a coroutine function that cached the result of another coroutine<\/a>, but it only cached successful calls. It didn&#8217;t cache failures, so if the inner coroutine fails, the outer one will simply try again the next time it is called. But what if we want to call the inner coroutine only once and cache the failure, too?<\/p>\n<p>We now have three states: Never tried, tried with success (and cache the success result), and tried with failure (and cache the failure). We can represent that as a variant with three types, using <code>std::<wbr \/>monostate<\/code> to represent the &#8220;never tried&#8221; state.\u00b9<\/p>\n<pre>struct Widget : WidgetT&lt;Widget&gt;\r\n{\r\n    std::variant&lt;std::monostate, winrt::Thing, std::exception_ptr&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 haven't tried, then this is our chance.\r\n        if (m_thing.holds_alternative&lt;std::monostate&gt;()) {\r\n            try {\r\n                m_thing = co_await GetThingWorkerAsync();\r\n            } catch (...) {\r\n                m_thing = std::current_exception();\r\n            }\r\n        }\r\n\r\n        \/\/ Return the cached result or cached failure.\r\n        if (auto thing = std::get_if&lt;winrt::Thing&gt;(&amp;m_thing)) {\r\n            co_return *thing;\r\n        } else {\r\n            std::rethrow_exception(std::get&lt;std::exception_ptr&gt;());\r\n        }\r\n    }\r\n};\r\n<\/pre>\n<p>After getting past our serialization, we check whether the <code>m_thing<\/code> holds a <code>std::<wbr \/>monostate<\/code>, meaning that we haven&#8217;t tried getting the thing yet. If so, then this is the first time through the function, so we will call <code>Get\u00adThing\u00adWorker\u00adAsync<\/code> and save the answer in the <code>m_thing<\/code>. If the call fails, then we save the exception in the <code>m_thing<\/code>.<\/p>\n<p>Regardeless of whether this is the first or subsequent call, we know that by the time we get past the first <code>if<\/code>, the <code>m_thing<\/code> is definitely not a <code>std::<wbr \/>monostate<\/code>. If it has a <code>winrt::<wbr \/>Thing<\/code>, then we return that cached thing. Otherwise, it must be a <code>std::<wbr \/>exception_<wbr \/>ptr<\/code>, so we rethrow that exception.<\/p>\n<p>If we know that <code>Get\u00adThing\u00adWorker\u00adAsync<\/code> never succeeds with <code>nullptr<\/code>, we can simplify the code by having separate variables (one for the non-null successful result and one for the exception pointer on failure), knowing that at most one of them will be non-null. And if both are null, then it means we haven&#8217;t attempted the call yet.<\/p>\n<pre>struct Widget : WidgetT&lt;Widget&gt;\r\n{\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">winrt::Thing m_thing{ nullptr };<\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">std::exception_ptr m_ex;        <\/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 haven't tried, then this is our chance.\r\n        if (<span style=\"border: solid 1px currentcolor;\">!m_thing &amp;&amp; !m_ex<\/span>) {\r\n            try {\r\n                m_thing = co_await GetThingWorkerAsync();\r\n                <span style=\"border: solid 1px currentcolor;\">assert(m_thing);<\/span>\r\n            } catch (...) {\r\n                <span style=\"border: solid 1px currentcolor;\">m_ex<\/span> = std::current_exception();\r\n            }\r\n        }\r\n\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">\/\/ Return the cached result or cached failure.<\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">if (m_thing) {                                <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    co_return m_thing;                        <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">} else {                                      <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    std::rethrow_exception(m_ex);             <\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                             <\/span>\r\n    }\r\n};\r\n<\/pre>\n<p>\u00b9 Bonus reading about <code>std::<wbr \/>monostate<\/code>: <a title=\"What's the point of std::monostate? You can't do anything with it!\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240708-00\/?p=109959\"> What&#8217;s the point of <code>std::<wbr \/>monostate<\/code>? You can&#8217;t do anything with it<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A variation where we try only once.<\/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-112368","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>A variation where we try only once.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112368","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=112368"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112368\/revisions"}],"predecessor-version":[{"id":112369,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112368\/revisions\/112369"}],"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=112368"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112368"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112368"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}