{"id":106261,"date":"2022-02-16T07:00:00","date_gmt":"2022-02-16T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106261"},"modified":"2022-02-16T07:07:21","modified_gmt":"2022-02-16T15:07:21","slug":"20220216-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220216-00\/?p=106261","title":{"rendered":"COM asynchronous interfaces, part 3: Abandoning the operation after a timeout"},"content":{"rendered":"<p>Last time, we <a title=\"COM asynchronous interfaces, part 2: Abandoning the operation\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220215-00\/?p=106253\"> learned how to abandon an asynchronous operation<\/a>. But maybe we don&#8217;t want to fire and forget so much as wait for a while before finally giving up.<\/p>\n<p>You can check on the completion state of the asynchronous call by using the <code>ISynchronize<\/code> interface on the call object. Today, we&#8217;re going to use the <code>Wait<\/code> method to wait for the call to complete, with a timeout.<\/p>\n<p>Let&#8217;s make these changes to our program.<\/p>\n<pre>int main(int, char**)\r\n{\r\n  winrt::init_apartment(winrt::apartment_type::multi_threaded);\r\n\r\n  auto pipe = CreateSlowPipeOnOtherThread();\r\n\r\n  winrt::com_ptr&lt;::AsyncIPipeByte&gt; call;\r\n  auto factory = pipe.as&lt;ICallFactory&gt;();\r\n  winrt::check_hresult(factory-&gt;CreateCall(\r\n    __uuidof(::AsyncIPipeByte), nullptr,\r\n    __uuidof(::AsyncIPipeByte),\r\n    reinterpret_cast&lt;::IUnknown**&gt;(call.put())));\r\n\r\n  printf(\"Beginning the Push\\n\");\r\n  BYTE buffer[15] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,\r\n                      11, 12, 13, 14, 15 };\r\n  winrt::check_hresult(call-&gt;Begin_Push(buffer, 15));\r\n\r\n  <span style=\"color: blue;\">printf(\"Waiting up to 250ms...\\n\");\r\n  auto sync = call.as&lt;::ISynchronize&gt;();\r\n  if (sync-&gt;Wait(COWAIT_DEFAULT, 250) == S_OK) {<\/span>\r\n    auto hr = call-&gt;Finish_Push();\r\n    printf(\"Pushed, result is %08x\\n\", hr);\r\n  <span style=\"color: blue;\">} else {\r\n    printf(\"Took too long!\\n\");\r\n    \/\/ abandon the operation\r\n  }<\/span>\r\n\r\n  <span style=\"color: blue;\">Sleep(2000); \/\/ so we can see the other thread finish<\/span>\r\n  return 0;\r\n}\r\n<\/pre>\n<p>This time, instead of abandoning the operation immediately, we ask <code>ISynchronize::Wait<\/code> to wait up to 250ms for the call to complete. If it does, then we call <code>Finish_<wbr \/>Push<\/code> to get the result of the <code>Push()<\/code> call. If it doesn&#8217;t, then we just abandon the operation.<\/p>\n<p>This works, but it could be better. Observe that even though we abandoned the operation, the <code>SlowPipe<\/code> still goes through with the <code>Push<\/code>. Instead of abandoning the operation, we can cancel it, to tell the server that it should stop doing any further work on this operation. The server can call <code>CoTestCancel()<\/code> periodically to see if the operation has been cancelled, and if so, stop and return <code>RPC_<wbr \/>E_<wbr \/>CALL_<wbr \/>CANCELED<\/code>.<\/p>\n<pre>struct SlowPipe :\r\n    winrt::implements&lt;SlowPipe, ::IPipeByte, winrt::non_agile&gt;\r\n{\r\n  \/\/ exit the STA thread when we destruct\r\n  ~SlowPipe() {  PostQuitMessage(0); }\r\n\r\n  STDMETHODIMP Pull(BYTE* buffer, ULONG size, ULONG* written)\r\n  {\r\n    <span style=\"color: blue;\">HRESULT hr = S_OK;<\/span>\r\n    printf(\"Pulling %lu bytes...\\n\", size);\r\n    ULONG index;\r\n    for (index = 0; index &lt; size \/ 2; index++) {\r\n      <span style=\"color: blue;\">if (CoTestCancel() == RPC_E_CALL_CANCELED) {\r\n          hr = RPC_E_CALL_CANCELED;\r\n          break;\r\n      }<\/span>\r\n      Sleep(100);\r\n      buffer[index] = 42;\r\n      printf(\"Pulled byte %lu of %lu\\n\", index, size);\r\n    }\r\n    *written = index;\r\n    printf(\"Finished pulling %lu% of %lu bytes<span style=\"color: blue;\">, hr = %08x<\/span>\\n\",\r\n            index, size<span style=\"color: blue;\">, hr<\/span>);\r\n    return <span style=\"color: blue;\">hr<\/span>;\r\n  }\r\n\r\n  STDMETHODIMP Push(BYTE* buffer, ULONG size)\r\n  {\r\n    <span style=\"color: blue;\">HRESULT hr = S_OK;<\/span>\r\n    printf(\"Pushing %lu bytes...\\n\", size);\r\n    ULONG index;\r\n    for (index = 0; index &lt; size; index++) {\r\n      <span style=\"color: blue;\">if (CoTestCancel() == RPC_E_CALL_CANCELED) {\r\n          hr = RPC_E_CALL_CANCELED;\r\n          break;\r\n      }<\/span>\r\n      Sleep(100);\r\n      printf(\"Pushed byte %08x\\n\", buffer[index]);\r\n    }\r\n    printf(\"Finished pushing %lu bytes<span style=\"color: blue;\">, hr = %08x<\/span>\\n\",\r\n           size, <span style=\"color: blue;\">hr<\/span>);\r\n    return <span style=\"color: blue;\">hr<\/span>;\r\n  }\r\n};\r\n<\/pre>\n<p>Our server now periodically checks whether the call was cancelled, and if so, it abandons the remainder of the operation. The <code>Pull<\/code> still reports the partial result, in case the caller cares.<\/p>\n<p>Now we can issue a cancellation from the main thread and see how it alters the behavior of the server.<\/p>\n<pre>int main(int, char**)\r\n{\r\n  winrt::init_apartment(winrt::apartment_type::multi_threaded);\r\n\r\n  auto pipe = CreateSlowPipeOnOtherThread();\r\n\r\n  winrt::com_ptr&lt;::AsyncIPipeByte&gt; call;\r\n  auto factory = pipe.as&lt;ICallFactory&gt;();\r\n  winrt::check_hresult(factory-&gt;CreateCall(\r\n    __uuidof(::AsyncIPipeByte), nullptr,\r\n    __uuidof(::AsyncIPipeByte),\r\n    reinterpret_cast&lt;::IUnknown**&gt;(call.put())));\r\n\r\n  printf(\"Beginning the Push\\n\");\r\n  BYTE buffer[15] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,\r\n                      11, 12, 13, 14, 15 };\r\n  winrt::check_hresult(call-&gt;Begin_Push(buffer, 15));\r\n\r\n  printf(\"Waiting up to 250ms...\\n\");\r\n  auto sync = call.as&lt;::ISynchronize&gt;();\r\n  if (sync-&gt;Wait(COWAIT_DEFAULT, 250) == S_OK) {\r\n    auto hr = call-&gt;Finish_Push();\r\n    printf(\"Pushed, result is %08x\\n\", hr);\r\n  } else {\r\n    printf(\"Took too long!\\n\");\r\n    <span style=\"color: blue;\">call.as&lt;::ICancelMethodCalls&gt;()-&gt;Cancel(0);<\/span>\r\n  }\r\n\r\n  Sleep(2000); \/\/ so we can see the other thread finish\r\n  return 0;\r\n}\r\n<\/pre>\n<p>This time, instead of abandoning the operation, we ask <code>ICancel\u00adMethod\u00adCalls::<wbr \/>Cancel<\/code> to cancel it with a timeout of zero, which means &#8220;immediately.&#8221; If you run this version of the program, you&#8217;ll see that the slow pipe responds to the cancellation by abandoning the operation partway through.<\/p>\n<p>At this point, we realize that we didn&#8217;t need <code>ISynchronize<\/code> at all. We could just have gone straight to <code>ICancel\u00adMethod\u00adCalls::<wbr \/>Cancel<\/code>, assuming we are willing to accept the fact that the <code>ICancel\u00adMethod\u00adCalls::<wbr \/>Cancel<\/code> method takes the timeout in seconds rather than milliseconds.<\/p>\n<pre>int main(int, char**)\r\n{\r\n  winrt::init_apartment(winrt::apartment_type::multi_threaded);\r\n\r\n  auto pipe = CreateSlowPipeOnOtherThread();\r\n\r\n  winrt::com_ptr&lt;::AsyncIPipeByte&gt; call;\r\n  auto factory = pipe.as&lt;ICallFactory&gt;();\r\n  winrt::check_hresult(factory-&gt;CreateCall(\r\n    __uuidof(::AsyncIPipeByte), nullptr,\r\n    __uuidof(::AsyncIPipeByte),\r\n    reinterpret_cast&lt;::IUnknown**&gt;(call.put())));\r\n\r\n  printf(\"Beginning the Push\\n\");\r\n  BYTE buffer[15] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,\r\n                      11, 12, 13, 14, 15 };\r\n  winrt::check_hresult(call-&gt;Begin_Push(buffer, 15));\r\n\r\n  <span style=\"color: blue;\">printf(\"Waiting up to 1 second...\\n\");\r\n  call.as&lt;::ICancelMethodCalls&gt;()-&gt;Cancel(1);\r\n\r\n  auto hr = call-&gt;Finish_Push();\r\n  printf(\"Pushed, result is %08x\\n\", hr);<\/span>\r\n\r\n  Sleep(2000); \/\/ so we can see the other thread finish\r\n  return 0;\r\n}\r\n<\/pre>\n<p>The <code>Cancel()<\/code> method waits for the timeout, in case the operation completes in time. If not, then it issues a cancellation and returns immediately. It doesn&#8217;t wait for the server to acknowledge the cancellation; it just issues the cancellation and marks the operation locally as having been cancelled, so that calling the <code>Finish_<\/code> method will return <code>RPC_<wbr \/>E_<wbr \/>CALL_<wbr \/>CANCELED<\/code> immediately.<\/p>\n<p>You can run the program again, but with a 2-second timeout to see the operation run to completion before the timeout elapses.<\/p>\n<p>So far, we&#8217;ve just been sitting around doing nothing while waiting for the operation to complete. Next time, we&#8217;ll try doing work in parallel.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Waiting a little while, but not forever.<\/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-106261","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Waiting a little while, but not forever.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106261","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=106261"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106261\/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=106261"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106261"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106261"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}