{"id":112361,"date":"2026-05-27T07:00:00","date_gmt":"2026-05-27T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112361"},"modified":"2026-05-27T13:10:58","modified_gmt":"2026-05-27T20:10:58","slug":"20260527-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260527-00\/?p=112361","title":{"rendered":"Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 1"},"content":{"rendered":"<p>Suppose you have a coroutine method called <code>GetThingAsync()<\/code>, and you want to do the work of getting &#8220;something&#8221; only once and caching the result. Here&#8217;s the version that does no caching:<\/p>\n<pre>struct Widget : WidgetT&lt;Widget&gt;\r\n{\r\n    IAsyncOperation&lt;Thing&gt; GetThingAsync()\r\n    {\r\n        co_return co_await GetThingWorkerAsync();\r\n    }\r\n\r\n    \/\/ The business logic goes here\r\n    IAsyncOperation&lt;Result&gt; GetThingWorkerAsync();\r\n};\r\n<\/pre>\n<p>Now, if this code were written in C#, we could take advantage of the fact that C# projects the Windows Runtime <code>IAsyncOperation<\/code> as a <code>Task<\/code>, and <code>Task<\/code> objects support being awaited on multiple times.<\/p>\n<pre>async Task&lt;Thing&gt; GetThingAsync()\r\n{\r\n    lock (m_lock) {\r\n        \/\/ First person to call GetThingAsync starts the task.\r\n        if (m_task == null) {\r\n            m_task = GetThingWorker();\r\n        }\r\n    }\r\n    return await m_getThingTask;\r\n}\r\n<\/pre>\n<p>But we don&#8217;t have that in C++\/WinRT.<\/p>\n<p>One customer tried to build a solution out of <code>winrt::<wbr \/>resume_<wbr \/>on_<wbr \/>signal<\/code>, perhaps inspired by <a title=\"Creating a co_await awaitable signal that can be awaited multiple times, part 1\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210301-00\/?p=104914\"> one of my earlier explorations of this topic<\/a>.<\/p>\n<pre>\/\/ Don't use this code. See discussion.\r\nstruct Widget : WidgetT&lt;Widget&gt;\r\n{\r\n    winrt::Thing m_thing{ nullptr };\r\n    winrt::IAsyncOperation&lt;winrt::Thing&gt; m_task{ nullptr };\r\n    wil::unique_event m_finished{ wil::EventOptions::ManualReset }; \/* initially unsignaled *\/\r\n    bool m_busy{ false };\r\n    std::mutex m_mutex;\r\n\r\n    IAsyncOperation&lt;winrt::Thing&gt; GetThingAsync()\r\n    {\r\n        bool shouldStart;\r\n        {\r\n            std::lock_guard guard(m_mutex);\r\n            if (m_thing != nullptr) {\r\n                \/\/ Operation has finished.\r\n                co_return m_thing;\r\n            } else if (m_busy) {\r\n                \/\/ Operation has started but not yet finished.\r\n                shouldStart = false;\r\n            } else {\r\n                \/\/ Operation hasn't even started.\r\n                m_busy = true;\r\n                shouldStart = true;\r\n            }\r\n        }\r\n\r\n        auto lifetime = get_strong();\r\n\r\n        if (shouldStart) {\r\n            auto task = GetThingWorker();\r\n            m_finished.ResetEvent();\r\n            task.Completed([weak = get_weak(), this](auto&amp;&amp;, auto&amp;&amp;) {\r\n                if (auto strong = weak.get()) {\r\n                    m_busy = false;\r\n                    m_finished.SetEvent();\r\n                }\r\n            });\r\n            m_task = std::move(task);\r\n        }\r\n\r\n        co_await winrt::resume_on_signal(m_finished.get());\r\n\r\n        {\r\n            std::lock_guard guard(m_mutex);\r\n            if (m_thing == nullptr &amp;&amp; m_task) {\r\n                m_thing = m_task.GetResults();\r\n            }\r\n        }\r\n\r\n        co_return m_thing;\r\n    }\r\n\r\n};\r\n<\/pre>\n<p>The idea is that the first time <code>Get\u00adThing\u00adAsync()<\/code> is called, we start the real task and then arrange to clear the busy flag and set the <code>m_finished<\/code> event when the task completes. Subsequent callers will see that the task has already started and will not start it again. And subsequent calls which occur after the task has completed will see that we already have a <code>m_thing<\/code> and return it immediately.<\/p>\n<p>Everybody then waits for the <code>m_finished<\/code> event, and then whoever manages to enter the mutex first gets the result and saves it. Finally, everybody returns whatever is in <code>m_thing<\/code>, which should be the result of the task.<\/p>\n<p>From the observation that they set <code>m_busy<\/code> back to false when the task completes, and they reset the <code>m_finished<\/code> event each time they start the task, I conclude that their intention was to allow multiple attempts to get the &#8220;something&#8221; if a previous attempt fails.<\/p>\n<p>Okay, so let&#8217;s see what could go wrong.<\/p>\n<p>For one thing, we see a data race because the completion lambda modifies <code>m_busy<\/code> outside the mutex. So we should at least protect that with a mutex.<\/p>\n<p>Another problem is that this code is not exception-safe. If <code>Get\u00adThing\u00adWorker\u00adAsync<\/code> throws an exception before returning an <code>IAsync\u00adOperation<\/code>, then the <code>m_busy<\/code> flag is set and gets stuck there. This means that nobody else will try to start the task, and the <code>m_task<\/code> remains null, so all subsequent callers just fall through and return a null <code>Thing<\/code>, which may not be something that the callers are expecting. (I mean, this code certainly doesn&#8217;t handle the case where <code>Get\u00adThing\u00adWorker\u00adAsync<\/code> produces a null result because it thinks that a null <code>m_thing<\/code> means that we should try again instead of &#8220;I successfully got nothing.&#8221;)<\/p>\n<p>There&#8217;s also a race condition if the task completes just as somebody calls <code>Get\u00adThing\u00adAsync<\/code>:<\/p>\n<table style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td style=\"border: solid 1px currentcolor;\">Thread 1<\/td>\n<td style=\"border: solid 1px currentcolor;\">Thread 2<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px currentcolor;\"><tt>GetThingAsync<\/tt> called<br \/>\n<tt>m_busy = true<\/tt><br \/>\n<tt>shouldStart = true<\/tt><br \/>\n<tt>GetThingWorkerAsync()<\/tt><br \/>\n<tt>m_finished.ResetEvent()<\/tt><br \/>\n<tt>task.Completed(...)<\/tt><br \/>\n<tt>m_task = std::move(task)<\/tt><br \/>\n<tt>co_await resume_on_signal<\/tt><\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px currentcolor;\">(task completes)<br \/>\n<tt>m_busy = false<\/tt><\/td>\n<td style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px currentcolor;\">\u00a0<\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>GetThing<\/tt> called<br \/>\n<tt>m_busy = true<\/tt><br \/>\n<tt>shouldStart = true<\/tt><br \/>\n<tt>GetThingWorkerAsync<\/tt><br \/>\n<tt>m_finished.ResetEvent()<\/tt><br \/>\n<tt>task.Completed(...)<\/tt><br \/>\n<tt>m_task = std::move(task)<\/tt><br \/>\n<tt>co_await resume_on_signal<\/tt><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px currentcolor;\"><tt>m_finished.SetEvent()<\/tt><br \/>\n(completion handler returns)<\/td>\n<td style=\"border: solid 1px currentcolor;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px currentcolor;\"><tt>m_thing = m_task.GetResults()<\/tt><\/td>\n<td style=\"border: solid 1px currentcolor;\">\u00a0<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Notice that the second call to <code>Get\u00adThing\u00adAsync<\/code> happens after <code>m_busy<\/code> has been reset, but before we have signaled the event. This creates a window inside which another thread calls <code>Get\u00adThing\u00adAsync<\/code> and tries to start the task again. The second calls assignment to <code>m_task<\/code> overwrites the one from the first call, and then when the first caller tries to get the results, it gets them from the wrong task.<\/p>\n<p>But really, this code is trying too hard. We&#8217;ll look at a simpler version next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Caching the result and knowing when the cache is valid.<\/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-112361","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Caching the result and knowing when the cache is valid.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112361","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=112361"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112361\/revisions"}],"predecessor-version":[{"id":112362,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112361\/revisions\/112362"}],"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=112361"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112361"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112361"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}