{"id":109727,"date":"2024-05-06T07:00:00","date_gmt":"2024-05-06T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109727"},"modified":"2024-05-06T06:49:34","modified_gmt":"2024-05-06T13:49:34","slug":"20240506-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240506-00\/?p=109727","title":{"rendered":"Awaiting a set of handles with a timeout, part 5: Generalizing the awaiter"},"content":{"rendered":"<p>Last time, we <a title=\"Awaiting a set of handles with a timeout, part 4: Building our own awaiter\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240503-00\/?p=109725\"> created an awaiter that could await an array of handles<\/a>. But it&#8217;s often the case that you have a bunch of handles in some other form, such as a pair of iterators, which is a common currency in the C++ standard library.<\/p>\n<p>First, let&#8217;s generalize our version to take a pair of iterators.<\/p>\n<pre>struct resume_all_awaiter\r\n{\r\n    \u27e6 data members unchanged \u27e7\r\n\r\n    <span style=\"border: solid 1px currentcolor;\">template&lt;typename Iter&gt;<\/span>\r\n    awaiter(<span style=\"border: solid 1px currentcolor;\">Iter first, Iter last<\/span>,\r\n        std::optional&lt;TimeSpan&gt; timeout) :\r\n        <span style=\"border: dashed 1px currentcolor; border-bottom: none;\">\/\/ <span style=\"text-decoration: line-through;\">m_remaining(size),<\/span><\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">\/\/ <span style=\"text-decoration: line-through;\">m_states(size),<\/span>   <\/span>\r\n        <span style=\"border: dashed 1px currentcolor; border-top: none;\">\/\/ <span style=\"text-decoration: line-through;\">m_results(size),<\/span>  <\/span>\r\n        m_timeout(timeout)\r\n    {\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">std::transform(first, last, std::back_inserter(m_states),<\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    [](HANDLE h) { state s; s.m_handle = h; return s; });<\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">create_waits();                                          <\/span>\r\n    }\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">void create_waits()                                          <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">{                                                            <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    if (m_states.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;\">\u00a0                                                            <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    auto size = static_cast&lt;uint32_t&gt;(m_states.size());      <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    m_remaining.store(size, std::memory_order_relaxed);      <\/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;\">        auto&amp; s = m_states[index];                           <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        s.m_parent = this;                                   <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        s.m_result = &amp;m_results[index];                      <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        s.m_wait.reset(winrt::check_pointer(                 <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">            CreateThreadpoolWait(callback, &amp;s, nullptr)));   <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    }                                                        <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                            <\/span>\r\n\r\n    \u27e6 other methods unchanged \u27e7\r\n};\r\n\r\n<span style=\"border: solid 1px currentcolor;\">template&lt;typename Iter&gt;<\/span>\r\nauto resume_on_all_signaled(<span style=\"border: solid 1px currentcolor;\">Iter first, Iter last,<\/span>\r\n    std::optional&lt;winrt::Windows::Foundation::TimeSpan&gt; timeout\r\n        = std::nullopt)\r\n{\r\n    return resume_all_awaiter(<span style=\"border: solid 1px currentcolor;\">first, last<\/span>, timeout);\r\n}\r\n<\/pre>\n<p>First, we record all of the handles that we were given, in the same number of <code>resume_<wbr \/>all_<wbr \/>state<\/code> objects. We can&#8217;t call <code>Create\u00adThreadpool\u00adWait<\/code> yet, because we want to use a pointer to the <code>resume_<wbr \/>all_<wbr \/>state<\/code> as the context pointer, which means we have to wait until all of the elements have been pushed onto the vector before we start taking their addresses. Otherwise, they will move when the vector expands its capacity.<\/p>\n<p>We build up the vector by pushing handles one at a time. We can&#8217;t preallocate the vector because the iterator may support only forward iteration, so the only way to find out how many handles there are is to increment the iterator all the way to the end. And it may be only an input iterator, which means you can walk through the collection only once.<\/p>\n<p>That value becomes <code>m_remaining<\/code>, the number of handles we are still waiting for. We also use that value to size the C-style array of <code>bool<\/code>s that will be used to hold the results.<\/p>\n<p>After vector has been filled with handles, we can initialize the rest of the state and create the wait objects, confident that the <code>resume_<wbr \/>all_<wbr \/>state<\/code> objects won&#8217;t move any more.<\/p>\n<p>To reduce code size, the second half of the calculations are factored into a helper method, which can be shared among different specializations of the constructor.<\/p>\n<p>We can add a helper method to simplify the case where the handles are already being held in an iterable container:<\/p>\n<pre>template&lt;typename Container = std::initializer_list&lt;HANDLE&gt;&gt;\r\nauto resume_on_all_signaled(Container const&amp; c,\r\n    std::optional&lt;winrt::Windows::Foundation::TimeSpan&gt; timeout\r\n        = std::nullopt)\r\n{\r\n    return resume_on_all_signaled(\r\n        std::begin(c), std::end(c), timeout);\r\n}\r\n<\/pre>\n<p>Since we are binding to a <code>const&amp;<\/code>, the container parameter might be a temporary. This was the important case I mentioned last time.<\/p>\n<p>We give a default type of <code>std::initializer_list&lt;HANDLE&gt;<\/code> so that you can write this:<\/p>\n<pre>co_await resume_on_all_signaled({ handle1, handle2 });\r\n<\/pre>\n<p>Next time, we&#8217;ll make this code more efficient in the case that the iterators are random-access, or at least random-access-like.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Fitting into existing patterns.<\/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-109727","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Fitting into existing patterns.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109727","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=109727"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109727\/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=109727"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109727"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109727"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}