{"id":106486,"date":"2022-04-15T07:00:00","date_gmt":"2022-04-15T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106486"},"modified":"2022-04-15T07:09:49","modified_gmt":"2022-04-15T14:09:49","slug":"20220415-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220415-00\/?p=106486","title":{"rendered":"How can I <CODE>co_await<\/CODE> on a Windows Runtime async action or operation with a timeout?"},"content":{"rendered":"<p>Say you want to <code>co_await<\/code> on an <code>IAsyncOperation<\/code> but also want to abandon the await if it takes too long.<\/p>\n<pre>auto widgetOperation = GetWidgetAsync();\r\nif (winrt::wait_for(widgetOperation, 15s) == AsyncStatus::Started)\r\n{\r\n    \/\/ timed out\r\n    widgetOperation.Cancel(); \/\/ abandon the operation\r\n} else {\r\n    \/\/ will throw if operation failed or was cancelled\r\n    auto widget = widgetOperation.GetResults();\r\n}\r\n<\/pre>\n<p>The downside of this approach is that it blocks the thread for 15 seconds. This is bad enough for a background thread, since you are holding a thread hostage for the duration, but it&#8217;s really bad for a UI thread, since it makes your UI hang.<\/p>\n<p>Instead, you can use <code>when_any<\/code> which creates a new <code>IAsyncXxx<\/code> that completes when any of its parameters completes. The first parameter is the <code>widgetOperation<\/code>, and the second is a coroutine that returns the same type of operation, but completes after a set timeout.<\/p>\n<pre>auto widgetOperation = GetWidgetAsync();\r\nauto widgetTimeout = [] -&gt; IAsyncOperation&lt;Widget&gt;\r\n    {\r\n        co_await winrt::resume_after(15s);\r\n        co_return nullptr;\r\n    }();\r\nauto widget = co_await winrt::when_any(widgetOperation, widgetTimeout);\r\n\r\nwidgetOperation.Cancel();\r\nwidgetTimeout.Cancel();\r\n\r\nif (!widget) {\r\n    \/\/ timed out or GetWidgetAsync() returned nullptr\r\n} else {\r\n    \/\/ GetWidgetAsync() produced a widget\r\n}\r\n<\/pre>\n<p>We create two <code>IAsyncOperation&lt;Widget&gt;<\/code>s. One is the operation we care about (<code>GetWidgetAsync()<\/code>) and the other is an operation that waits 15 seconds, and then completes with <code>nullptr<\/code>.<\/p>\n<p>We then use <code>winrt::<wbr \/>when_any<\/code> to await until anything completes. Once anything completes, we cancel everything. This has no effect on asynchronous operations that are completed, but it tells the still-incomplete operations to abandon their work if they can. Cancelling everything isn&#8217;t technically necessary, but it does avoid doing extra work that is going to be ignored anyway.<\/p>\n<p>After all the tidying is done, we see if we actually got something. If the <code>Get\u00adWidget\u00adAsync()<\/code> is taking too long, then the winning operation will be the <code>widgetTimeout<\/code>, and it completes with <code>nullptr<\/code>. If the <code>Get\u00adWidget\u00adAsync()<\/code> finishes in under 15 seconds, then it will be the winning operation, and the result will be the thing it produced. That thing it produced could have been <code>nullptr<\/code>, so you can&#8217;t distinguish it from a timeout, but the idea here is that you picked the <code>co_return<\/code> at the end of the <code>widgetTimeout<\/code> lambda so that <code>nullptr<\/code> corresponds to what you would have wanted to do in the case of a timeout.<\/p>\n<p>If you really need to distinguish between completing with <code>nullptr<\/code> and timing out, you could add an explicit flag.<\/p>\n<pre><span style=\"color: blue;\">auto timedOut = std::make_shared&lt;bool&gt;();<\/span>\r\nauto widgetOperation = GetWidgetAsync();\r\nauto widgetTimeout = []<span style=\"color: blue;\">(auto timedOut)<\/span> -&gt; IAsyncOperation&lt;Widget&gt;\r\n    {\r\n        co_await winrt::resume_after(15s);\r\n        <span style=\"color: blue;\">*timedOut = true;<\/span>\r\n        co_return nullptr;\r\n    }(timedOut);\r\nauto widget = co_await winrt::when_any(widgetOperation, widgetTimeout);\r\n\r\nwidgetOperation.Cancel();\r\nwidgetTimeout.Cancel();\r\n\r\nif (*timedOut) {\r\n    \/\/ timed out\r\n} else {\r\n    \/\/ GetWidgetAsync() produced something (possibly nullptr)\r\n}\r\n<\/pre>\n<p>Or, perhaps more sneakily, you can check the status of the <code>widgetTimeout<\/code> operation.<\/p>\n<pre>auto widgetOperation = GetWidgetAsync();\r\nauto widgetTimeout = [] -&gt; IAsyncOperation&lt;Widget&gt;\r\n    {\r\n        co_await winrt::resume_after(15s);\r\n        co_return nullptr;\r\n    }();\r\nauto widget = co_await winrt::when_any(widgetOperation, widgetTimeout);\r\n<span style=\"color: blue;\">auto timedOut = widgetTimeout.Status() == AsyncStatus::Completed;<\/span>\r\n\r\nwidgetOperation.Cancel();\r\nwidgetTimeout.Cancel();\r\n\r\nif (<span style=\"color: blue;\">timedOut<\/span>) {\r\n    \/\/ timed out\r\n} else {\r\n    \/\/ GetWidgetAsync() produced something (possibly nullptr)\r\n}\r\n<\/pre>\n<p>We&#8217;ll expand upon this pattern in a few weeks, so stay tuned. (Or &#8220;tune out&#8221;, if that&#8217;s more to your liking.)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You can combine it with a timeout.<\/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-106486","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You can combine it with a timeout.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106486","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=106486"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106486\/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=106486"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106486"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106486"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}