{"id":107418,"date":"2022-11-17T07:00:00","date_gmt":"2022-11-17T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107418"},"modified":"2025-01-16T08:55:42","modified_gmt":"2025-01-16T16:55:42","slug":"20221117-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20221117-00\/?p=107418","title":{"rendered":"How soon is too soon to report progress from a C++\/WinRT coroutine that implements a Windows Runtime asynchronous operation with progress?"},"content":{"rendered":"<p>A customer discovered a problem with their C++\/WinRT coroutine that generates progress.<\/p>\n<pre>IAsyncOperationWithProgress&lt;UpdateResult, bool&gt;\r\n    SomeClass::UpdateAsync()\r\n{\r\n    auto lifetime = get_strong();\r\n    auto progress = co_await get_progress_token();\r\n\r\n    \/\/ Tell the caller that we are preparing\r\n    progress(false);\r\n\r\n    Prepare();\r\n\r\n    \/\/ Tell the caller that we have finished preparing\r\n    progress(true);\r\n\r\n    \/\/ ... more work ...\r\n\r\n    co_return UpdateResult(\/* something *\/);\r\n}\r\n<\/pre>\n<p>This is a simple coroutine that generates progress notifications to tell the caller when we have entered and exited the &#8220;Prepare&#8221; stage. The caller looks like this:<\/p>\n<pre>auto op = someClass.UpdateAsync();\r\nop.Progress([](auto&amp;&amp;, bool started)\r\n{\r\n    if (started) ReallyImportantFunction();\r\n});\r\nauto result = co_await op;\r\n<\/pre>\n<p>The expectation is that the two progress events would be received by the Progress event handler in the calling code. However, running the code in the debugger suggests that the <code>Update\u00adAsync<\/code> function runs synchronously until well past the two calls to <code>progress(...)<\/code>, which means that the caller has hooked up its Progress event handler too late, and it never realizes that the operation has reached the &#8220;started&#8221; state, and the <code>Really\u00adImportant\u00adFunction()<\/code> never runs.<\/p>\n<p>What&#8217;s going on, and what can we do about it?<\/p>\n<p>The customer&#8217;s analysis is correct. This is an inherent hazard of hot-start coroutines. Even if you add a <code>co_await resume_background()<\/code> or a <code>co_await resume_after(1s)<\/code> to allow the caller to regain control and hook up its Progress event handler, there&#8217;s still the chance that the caller hasn&#8217;t quite gotten around to doing it by the time you raise the &#8220;started&#8221; progress event.\u00b9<\/p>\n<p>You should try to design your operations so that progress reports are just a courtesy, and missing a progress report is not fatal.<\/p>\n<p>If you need guaranteed progress delivery, you can work around this behavior in a few ways.<\/p>\n<p>One idea is to use the ability for the caller to obtain provisional results:<\/p>\n<pre>\/\/ idl\r\n\r\nruntimeclass UpdateResult\r\n{\r\n    String NewName { get; };\r\n    Flavor NewFlavor { get; };\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">\/\/ Add a new member            <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">Boolean UpdateStarted { get; };<\/span>\r\n}\r\n\r\n\/\/ implementation\r\n\r\nIAsyncOperationWithProgress&lt;UpdateResult, bool&gt;\r\n    SomeClass::UpdateAsync()\r\n{\r\n    auto lifetime = get_strong();\r\n    auto progress = co_await get_progress_token();\r\n\r\n    \/\/ Set the provisional result and notify the caller.\r\n    UpdateResult result;\r\n    result.Started(false);\r\n    progress.set_result(result);\r\n    progress(false);\r\n\r\n    Prepare();\r\n\r\n    \/\/ Let the caller know that we have finished preparing.\r\n    result.Started(true);\r\n    progress(true);\r\n\r\n    \/\/ ... more work ...\r\n\r\n    co_return result;\r\n}\r\n<\/pre>\n<p>The idea here is that we report the progress in two ways. One is via the Progress event on the <code>IAsyncOperation<\/code>. The other is as a new property of the operation result.\u00b2<\/p>\n<p>Windows Runtime asynchronous operations with progress can report provisional results prior to completion, and callers can obtain those provision results by calling <code>GetResults()<\/code> while the operation is still running. On the implementation side, C++\/WinRT lets you publish provisional results by calling <code>set_result()<\/code> on the progress token.<\/p>\n<p>The consuming side would look like this:<\/p>\n<pre>auto op = someClass.UpdateAsync();\r\nop.Progress([](auto&amp;&amp;, bool started)\r\n{\r\n    if (started) ReallyImportantFunction();\r\n});\r\n\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">\/\/ Check if the update started before we were able             <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">\/\/ to register for the Progress event.                         <\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">if (op.GetResults().UpdateStarted()) ReallyImportantFunction();<\/span>\r\n\r\nauto result = co_await op;\r\n<\/pre>\n<p>Note that there is a race condition here where the update starts after we register the Progress event handler but before we check the provisional results. In that case, we will call <code>Really\u00adImportant\u00adFunction()<\/code> twice. In our customer&#8217;s case, it was harmless to call it twice, but in the more general case, you may need to add additional logic to avoid calling <code>Really\u00adImportant\u00adFunction()<\/code> twice.<\/p>\n<p>But really, you shouldn&#8217;t put critical information in the Progress event payload. Next time, we&#8217;ll look at alternate designs for critical progress notifications.<\/p>\n<p>\u00b9 For example, the caller thread may have lost its quantum just before it was able to hook up the Progress event handler.<\/p>\n<p>\u00b2 If the original operation result was not a runtime class, you can create a new runtime class that consists of the original operation result, plus the new <code>UpdateStarted<\/code> property. Alternatively, you can make the operation result a Windows Runtime <code>struct<\/code>, but Windows Runtime <code>struct<\/code>s are value types, so you will have to do a <code>progress.set_result()<\/code> each time you update the <code>result<\/code> to update the copy.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If a progress is sent to a forest but there&#8217;s no one there to hear it.<\/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-107418","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>If a progress is sent to a forest but there&#8217;s no one there to hear it.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107418","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=107418"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107418\/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=107418"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107418"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107418"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}