{"id":109721,"date":"2024-05-02T07:00:00","date_gmt":"2024-05-02T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109721"},"modified":"2024-05-02T11:09:28","modified_gmt":"2024-05-02T18:09:28","slug":"20240502-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240502-00\/?p=109721","title":{"rendered":"Awaiting a set of handles with a timeout, part 3: Going beyond two"},"content":{"rendered":"<p>Last time, we figured out <a title=\"Awaiting a set of handles with a timeout, part 2: Continuing with two\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240501-00\/?p=109718\"> how to await two handles with a common timeout<\/a>. Armed with what we learned from that ordeal, we can try to generalize it to an arbitrary number of handles.<\/p>\n<p>Our first attempt might go like this:<\/p>\n<pre>template&lt;typename Iter&gt;\r\nwil::task&lt;std::vector&lt;bool&gt;&gt;\r\n    resume_on_all_signaled(Iter first, iter last,\r\n        winrt::Windows::Foundation::TimeSpan timeout = {})\r\n{\r\n    using BoolOp = winrt::Windows::Foundation::IAsyncOperation&lt;bool&gt;;\r\n\r\n    std::vector&lt;BoolOp&gt; ops;\r\n    std::transform(first, last, std::back_inserter(ops),\r\n        [&amp;](HANDLE handle) {\r\n        return resume_on_one_signal(handle, timeout);\r\n    });\r\n\r\n    std::vector&lt;bool&gt; results;\r\n    for (auto&amp;&amp; op : ops) {\r\n        results.push_back(co_await winrt::resume_agile(op));\r\n    }\r\n\r\n    co_return std::move(results);\r\n}\r\n<\/pre>\n<p>The first step is to take the incoming handles and convert them to async operations, which we gather in a vector.<\/p>\n<p>Next, we await each of the awaiters and push the result onto a results vector.<\/p>\n<p>Finally, we return the results.<\/p>\n<p>Now, one of the really annoying things happening here is that we are using <code>vector&lt;bool&gt;<\/code>. <a title=\"Of what use is a type-dependent expression that is always false?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200312-00\/?p=103556\"> As I noted some time ago<\/a>, this specialization represents a packed bit array. This has made a lot of people very angry and <a href=\"https:\/\/isocpp.org\/blog\/2012\/11\/on-vectorbool\"> has been widely regarded<\/a> as a <a href=\"http:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2005\/n1847.pdf\"> bad move<\/a>.<\/p>\n<p>So let&#8217;s use a C-style array of <code>bool<\/code> instead. C++\/WinRT comes with one already, known as <code>winrt::<wbr \/>com_array<\/code>. We&#8217;ll use that.<\/p>\n<pre>template&lt;typename Iter&gt;\r\nwil::task&lt;<span style=\"border: solid 1px currentcolor;\">winrt::com_array&lt;bool&gt;<\/span>&gt;\r\n    resume_on_all_signaled(Iter first, iter last,\r\n        winrt::Windows::Foundation::TimeSpan timeout = {})\r\n{\r\n    using BoolOp = winrt::Windows::Foundation::IAsyncOperation&lt;bool&gt;;\r\n\r\n    std::vector&lt;BoolOp&gt; ops;\r\n\r\n    std::transform(first, last, std::back_inserter(ops),\r\n        [&amp;](HANDLE handle) {\r\n        return resume_on_one_signal(handle, timeout);\r\n    });\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">if (ops.size() &gt; ~0U \/ sizeof(bool)) {                   <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">  throw std::bad_alloc();                                <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">}                                                        <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">auto size = static_cast&lt;uint32_t&gt;(ops.size());           <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">auto results = winrt::com_array{ size };                 <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">for (auto index = 0U; index &lt; size; ++index) {           <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    results[index] =                                     <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">           co_await winrt::resume_agile(awaiters[index]);<\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                        <\/span>\r\n\r\n    co_return std::move(results);\r\n}\r\n<\/pre>\n<p>There is a serious issue: We behave erratically if an exception is thrown from <code>co_await<\/code>.<\/p>\n<p>An exception is possible because the awaiter returned by <code>resume_<wbr \/>on_<wbr \/>signal<\/code> will throw an exception if it cannot set up the threadpool wait. In that case, the state of the handles is indeterminate: Some of them may have been successfully waited, and others may not have been, and you don&#8217;t know which were and weren&#8217;t waited because the exception prevented you from receiving the results.<\/p>\n<p>Knowing which ones were awaited is important if any of the things you are waiting for are consumable, such as semaphores or auto-reset events.<\/p>\n<p>We&#8217;ll continue our investigation next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Generalizing what we learned last time.<\/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-109721","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Generalizing what we learned last time.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109721","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=109721"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109721\/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=109721"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109721"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109721"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}