{"id":108375,"date":"2023-06-27T07:00:00","date_gmt":"2023-06-27T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108375"},"modified":"2023-06-19T06:25:13","modified_gmt":"2023-06-19T13:25:13","slug":"20230627-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230627-00\/?p=108375","title":{"rendered":"How to wait for multiple C++ coroutines to complete before propagating failure, unhelpful lambda"},"content":{"rendered":"<p>Last time, we found <a title=\"How to wait for multiple C++ coroutines to complete before propagating failure, initial plunge\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230626-00\/?p=108373\"> a solution for waiting for multiple C++ coroutines to complete before propagating failure<\/a>, but it relied on <i>expansion statements<\/i>, which weren&#8217;t finished in time for C++20. We&#8217;ll have to find something that uses only features available in C++20, or even better, works in C++17.\u00b9<\/p>\n<p>The usual way to get expansion-like behavior is to use a lambda and the comma operator:<\/p>\n<pre>([&amp;](auto&amp; arg) {\r\n    \/* do something *\/\r\n}(args), ...);\r\n<\/pre>\n<p>So let&#8217;s try applying it to our &#8220;wait for all&#8221; coroutine function:<\/p>\n<pre>template&lt;typename... T&gt;\r\nIAsyncAction when_all_complete(T... asyncs)\r\n{\r\n    std::exception_ptr eptr;\r\n\r\n    ([&amp;] (auto&amp; async) {\r\n        try {\r\n            co_await async;\r\n        } catch (...) {\r\n            if (!eptr) {\r\n                eptr = std::current_exception();\r\n            }\r\n        }\r\n    }(asyncs), ...);\r\n\r\n    if (eptr) std::rethrow_exception(eptr);\r\n}\r\n<\/pre>\n<p>Sadly, this doesn&#8217;t work because the lambda performs a <code>co_await<\/code>, and <code>co_await<\/code> requires that you be in a coroutine.<\/p>\n<p>I guess we have to make the lambda a coroutine.<\/p>\n<pre>template&lt;typename... T&gt;\r\nIAsyncAction when_all_complete(T... asyncs)\r\n{\r\n    std::exception_ptr eptr;\r\n\r\n    auto each = [&amp;] (auto&amp; async) -&gt; IAsyncAction {\r\n        try {\r\n            co_await async;\r\n        } catch (...) {\r\n            if (!eptr) {\r\n                eptr = std::current_exception();\r\n            }\r\n        }\r\n    };\r\n\r\n    (co_await each(asyncs), ...);\r\n\r\n    if (eptr) std::rethrow_exception(eptr);\r\n}\r\n<\/pre>\n<p>One thing you might object to is the fact that we have a lambda coroutine with a capture. This is generally frowned upon, due to the risk of the lambda being destructed while suspended, but it works here because the lambda is not destructed until <code>when_<wbr \/>all_<wbr \/>complete<\/code> finishes executing all of its <code>co_await<\/code>s. Even if one of the calls to <code>co_await async<\/code> throws an exception, that exception is caught and saved in <code>eptr<\/code>, and the overall coroutine completes without an exception.<\/p>\n<p>It looks like we&#8217;ve done it, but there&#8217;s a catch: The threading model.<\/p>\n<p>In the original <code>when_all<\/code>, the parameters were each <code>co_await<\/code>ed directly in the main function, which means that if any of the <code>co_await<\/code>ed things changed threads, the thread change would remain in effect for the next <code>co_await<\/code>:<\/p>\n<pre>winrt::fire_and_forget example()\r\n{\r\n    co_await winrt::when_all(resume_background(), Something());\r\n}\r\n<\/pre>\n<p>Inside <code>when_all<\/code>, this expands into<\/p>\n<pre>auto first = resume_background();\r\nauto second = Something();\r\nco_await first;\r\nco_await second;\r\n<\/pre>\n<p>The <code>co_await first<\/code> performs a thread switch, and the <code>co_await second<\/code> executes on the new thread.<\/p>\n<p>This is different from our <code>when_<wbr \/>all_<wbr \/>complete<\/code> because that one wraps each <code>co_await<\/code> inside another <code>IAsyncAction<\/code>, which changes the threading behavior: In C++\/WinRT, <code>co_await<\/code>ing an <code>IAsyncAction<\/code> resumes on the same apartment that started the <code>co_await<\/code>. Since we wrapped each awaitable inside a lambda that produces an <code>IAsyncAction<\/code>, each <code>co_await<\/code> of the lambda will resume back on the original apartment, even if the original awaitable completed in a different apartment.<\/p>\n<p>Here&#8217;s a comparison: With expansion statements, we just <code>co_await<\/code> directly from the function.<\/p>\n<pre>IAsyncAction v1(async1, async2)\r\n{\r\n    co_await async1;\r\n    co_await async2;\r\n}\r\n<\/pre>\n<p>Suppose that <code>async1<\/code> completes on a different apartment. (For example, it might be an apartment-switching awaitable, like <code>resume_background<\/code>.) Then the <code>co_await async1<\/code> will complete on the other apartment, and the <code>co_await async2<\/code> therefore begins on that other apartment.<\/p>\n<p>On the other hand, with a lambda, we <code>co_await<\/code> from a lambda, and then <code>co_await<\/code> the lambda.<\/p>\n<pre>IAsyncAction v2(async1, async2)\r\n{\r\n    co_await [](auto&amp; async) -&gt; IAsyncAction {\r\n        co_await async;\r\n    }(async1);\r\n\r\n    co_await [](auto&amp; async) -&gt; IAsyncAction {\r\n        co_await async;\r\n    }(async2);\r\n}\r\n<\/pre>\n<p>First, we invoke the lambda, which does a <code>co_await async1<\/code>, which completes on some other apartment. However, since we <code>co_await<\/code> the lambda, and the lambda itself returns an <code>IAsyncAction<\/code>, the <code>co_await<\/code> of the lambda will complete back on the <i>original<\/i> apartment (because that&#8217;s how the <code>IAsyncAction<\/code> awaiter works). Next, we <code>co_await async2<\/code>, which now begins on the original apartment, not the apartment in which <code>async1<\/code> completed.<\/p>\n<p>Next time, we&#8217;ll try to get all the <code>co_await<\/code>s to happen in the main function, so that we can preserve the apartment-switching behavior of the original <code>when_all<\/code> function.<\/p>\n<p>Spoiler alert: Next time will be a failure.<\/p>\n<p>\u00b9 Yes, coroutines are not part of C++ until C++20, but the Microsoft Visual Studio compiler lets you opt into coroutine support while in C++17 mode, so &#8220;C++17 with optional coroutines&#8221; is the current baseline for C++\/WinRT.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The usual trick doesn&#8217;t work.<\/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-108375","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The usual trick doesn&#8217;t work.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108375","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=108375"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108375\/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=108375"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108375"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108375"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}