{"id":105797,"date":"2021-10-13T07:00:00","date_gmt":"2021-10-13T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105797"},"modified":"2021-10-13T06:55:28","modified_gmt":"2021-10-13T13:55:28","slug":"20211013-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20211013-00\/?p=105797","title":{"rendered":"Local variables are different from parameters in C++ coroutines"},"content":{"rendered":"<p>In C++, you generally think of parameters and local variables as equivalent. A parameter behaves like a conveniently-initialized local variable.\u00b9<\/p>\n<p>But not for coroutines.<\/p>\n<p>Let&#8217;s look at <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210331-00\/?p=105028\"> one of the early steps of the coroutine transformation<\/a> again:<\/p>\n<pre>return_type MyCoroutine(args...)\r\n{\r\n    <i>create coroutine state<\/i>\r\n    <i>copy parameters to coroutine frame<\/i>\r\n    promise_type p;\r\n    return_type task = p.get_return_object();\r\n\r\n    try {\r\n        co_await p.initial_suspend();\r\n        <i>coroutine function body<\/i>\r\n    } catch (...) {\r\n        p.unhandled_exception();\r\n    }\r\n    co_await p.final_suspend();\r\n    <i>destruct promise p<\/i>\r\n    <i>destruct parameters in coroutine frame<\/i>\r\n    <i>destroy coroutine state<\/i>\r\n}\r\n<\/pre>\n<p>Notice that local variables are destructed when we leave the scope of the coroutine function body. In other words, the local variables destruct when we exit the <code>try<\/code> block.<\/p>\n<p>On the other hand, the parameters are <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210412-00\/?p=105078\"> not destructed until the coroutine frame is destroyed<\/a>.<\/p>\n<p>Consider the following code:<\/p>\n<pre>winrt::IAsyncActionWithProgress&lt;int&gt; LoadAsync(std::shared_ptr&lt;S&gt; s)\r\n{\r\n    \/* do stuff *\/\r\n    co_return;\r\n}\r\n\r\nwinrt::fire_and_forget Example()\r\n{\r\n    auto temp = std::make_shared&lt;S&gt;();\r\n    auto action = LoadAsync(temp);\r\n    action.Progress(\r\n        [](auto&amp;&amp; sender, auto progress) { \/* report progress *\/ });\r\n    co_await action;\r\n    ProcessData(temp);\r\n    temp = nullptr; \/\/ finished with S\r\n\r\n    co_await RefreshAsync();\r\n}\r\n<\/pre>\n<p>This code creates an <code>S<\/code> object and loads data into it via a coroutine. That coroutine uses <code>IAsync\u00adAction\u00adWith\u00adProgress<\/code> as its return type, and the <code>Example<\/code> function takes advantage of that by listening for progress reports. It then <code>co_await<\/code>s the action to wait for the <code>Load\u00adAsync<\/code> coroutine to complete, while getting progress reports along the way.<\/p>\n<p>After the action completes, it processes the data and then nulls out the <code>temp<\/code> local variable to free the <code>S<\/code> object, since it&#8217;s not needed any more.<\/p>\n<p>Finally, the function performs a refresh so it updates with the new processed data.<\/p>\n<p>Do you see the error in the above analysis?<\/p>\n<p>Since <code>LoadAsync<\/code> receives the <code>std::shared_ptr&lt;S&gt;<\/code> as a parameter, that parameter is stored in the coroutine frame and is not destructed until the frame is destructed, which for <code>IAsyncAction<\/code> doesn&#8217;t happen until the <code>IAsyncAction<\/code> is destructed.<\/p>\n<p>In the above example, the <code>IAsyncAction<\/code> is stored into a local variable <code>action<\/code>, and that local variable doesn&#8217;t destruct until the end of the <code>Example<\/code> coroutine. The <code>S<\/code> object is being kept alive by the <code>s<\/code> parameter that was passed to the <code>Load\u00adAsync<\/code> function, and that is kept alive by the coroutine frame, and the coroutine frame is kept alive by the <code>action<\/code> variable.<\/p>\n<p>I discussed this issue from the point of view of the <code>Load\u00adAsync<\/code> function <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210413-00\/?p=105093\"> some time ago<\/a>. But now we&#8217;re looking at it from the caller&#8217;s point of view.<\/p>\n<p>As a caller of a coroutine, you should try to destruct the <code>IAsyncAction<\/code> as soon as you&#8217;re finished with it. Usually, this is done by never assigning to anything; just leave it as a compiler temporary, which destructs at the end of the statement.\u00b2 If you do assign it to a variable, you should null out the variable once you&#8217;ve finished using it:<\/p>\n<pre>winrt::fire_and_forget Example()\r\n{\r\n    auto temp = std::make_shared&lt;S&gt;();\r\n    auto action = LoadAsync(temp);\r\n    action.Progress(\r\n        [](auto&amp;&amp; sender, auto progress) { \/* report progress *\/ });\r\n    co_await action;\r\n    <span style=\"color: blue;\">action = nullptr;<\/span>\r\n    ProcessData(temp);\r\n    temp = nullptr; \/\/ finished with S\r\n\r\n    co_await RefreshAsync();\r\n}\r\n<\/pre>\n<p>Or otherwise arrange for the reference to be released, say by scoping it:<\/p>\n<pre>winrt::fire_and_forget Example()\r\n{\r\n    auto temp = std::make_shared&lt;S&gt;();\r\n    <span style=\"color: blue;\">{\r\n        auto action = LoadAsync(temp);\r\n        action.Progress(\r\n            [](auto&amp;&amp; sender, auto progress) { \/* report progress *\/ });\r\n        co_await action;\r\n    } \/\/ destruct the action<\/span>\r\n    ProcessData(temp);\r\n    temp = nullptr; \/\/ finished with S\r\n\r\n    co_await RefreshAsync();\r\n}\r\n<\/pre>\n<p>The nested scope presents a problem if the <code>co_await<\/code> returns a value, such as an <code>IAsync\u00adOperation<\/code>.<\/p>\n<p>Another solution is to create a helper function to avoid having to store the action in a local variable:<\/p>\n<pre>template&lt;typename Async, typename Handler&gt;\r\nAsync AttachProgress(Async sync, Handler&amp;&amp; handler)\r\n{\r\n    async.Progress(std::forward&lt;Handler&gt;(handler));\r\n    return async;\r\n}\r\n<\/pre>\n<p>Now you don&#8217;t have to name the object when attaching the progress handler, and the prevents the lifetime from being extended. It also gives you a chance to capture the coroutine result without having to worry about how to get the variable out of a nested scope.<\/p>\n<pre>winrt::fire_and_forget Example()\r\n{\r\n    auto temp = std::make_shared&lt;S&gt;();\r\n    <span style=\"color: blue;\">auto result = co_await AttachProgress(LoadAsync(temp),\r\n        [](auto&amp;&amp; sender, auto progress) { \/* report progress *\/ });<\/span>\r\n    ProcessData(temp);\r\n    temp = nullptr; \/\/ finished with S\r\n\r\n    co_await RefreshAsync();\r\n}\r\n<\/pre>\n<p>Another way to solve this problem is on the coroutine side: Move the value out of the parameter to a local variable. That way, it destructs with the locals, rather than hanging around in the parameter space. (Well, technically it&#8217;s still in the parameter space, but you made it relinquish control of its resources, so what&#8217;s left is empty.)<\/p>\n<pre>winrt::IAsyncActionWithProgress&lt;int&gt; LoadAsync(std::shared_ptr&lt;S&gt; s_param)\r\n{\r\n    <span style=\"color: blue;\">auto s = std::move(s_param);<\/span>\r\n\r\n    \/* do stuff *\/\r\n    co_return;\r\n}\r\n<\/pre>\n<p>Control of the <code>S<\/code> object has been moved out of the parameter by <code>std::move<\/code>&#8216;ing the <code>shared_ptr<\/code> into the local.<\/p>\n<p>If you wrote the coroutine, you can apply this principle, but if the coroutine was provided by something outside your control, then you can&#8217;t be sure how expensive it is to keep an already-completed coroutine around. Probably best to get rid of it as soon as possible.<\/p>\n<p>\u00b9 One difference is that parameters destruct in the context of the caller: If a parameter&#8217;s destructor throws an exception, the exception is thrown from the caller and cannot be caught by the called function. Mind you, throwing an exception from a destructor is a bad idea, so this distinction is unlikely to be significant in practice.<\/p>\n<p>\u00b2 Formally, it destructs at the end of the <i>full expression<\/i>, which is smaller than a statement.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You tend to think of them the same, but they destruct differently.<\/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-105797","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You tend to think of them the same, but they destruct differently.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105797","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=105797"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105797\/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=105797"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105797"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105797"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}