{"id":100725,"date":"2019-01-17T07:00:00","date_gmt":"2019-01-17T22:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/?p=100725"},"modified":"2021-02-08T17:21:08","modified_gmt":"2021-02-09T01:21:08","slug":"20190117-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190117-00\/?p=100725","title":{"rendered":"How do I get the effect of C#&#8217;s async void in a C++ coroutine? Part 2: Keeping track of the lifetimes"},"content":{"rendered":"<p><a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190116-00\/?p=100715\"> Last time<\/a>, we looked at how to write a function that formally returns <code>void<\/code> that nevertheless performs <code>co_await<\/code> operations. The function acts like a fire-and-forget, where the remainder of the task runs asynchronously after the first <code>co_await<\/code> that needs to suspend.<\/p>\n<p>To recap: We saw that the obvious single-function solution fails because of lifetime issues:<\/p>\n<pre>\/\/ Code in italics is wrong.\r\nvoid MyEventHandler(int a, int b)\r\n{\r\n  <span style=\"color: blue;\"><i>[=]() -&gt; Concurrency::task&lt;void&gt;<\/i>\r\n  {<\/span>\r\n    GetReady(a);\r\n    co_await GetSetAsync(b);\r\n    Go(a, b);\r\n  <span style=\"color: blue;\">}();<\/span>\r\n}\r\n<\/pre>\n<p>The problem is that the lambda is destroyed when the function returns, and then when the task tries to access it, it ends up trying to use a detroyed object, and that doesn&#8217;t end well.<\/p>\n<p>Functions that are converted to coroutines capture the function parameters and their local variables in a frame. The frame consists of the function&#8217;s formal parameters and local variables, so we need to transfer the captured things into the frame:<\/p>\n<pre>void MyClass::MyEventHandler(int a, int b)\r\n{\r\n  [](std::shared_ptr&lt;MyClass&gt; self, int a, int b)\r\n   -&gt; Concurrency::task&lt;void&gt;\r\n  {\r\n   self-&gt;GetReady(a);\r\n   co_await self-&gt;GetSetAsync(b);\r\n   self-&gt;Go(a, b);\r\n  }(this-&gt;shared_from_this(), a, b);\r\n}\r\n<\/pre>\n<p>However, this forces us to repeat <code>self<\/code> all the time. What we want is a way to get <code>this<\/code> to refer to the class whose method we are in, so we won&#8217;t have to do this <code>self<\/code> nonsense. But we also want a captureless lambda, so we won&#8217;t have the problem of a task trying to access a destroyed lambda.<\/p>\n<p>This is a contradiction. If you have a captureless lambda, then it cannot capture <code>this<\/code>. The problem looks hopeless.<\/p>\n<p>Until you realize that there&#8217;s nobody forcing you to do it all in just one lambda.<\/p>\n<p>So let&#8217;s use two lambdas. One of them captures <code>this<\/code> so it doesn&#8217;t have to carry <code>self<\/code> around. And then we put that lambda into the frame of the second lambda and invoke it. Then we invoke the second lambda to get the party started.<\/p>\n<pre>void MyClass::MyEventHandler(int a, int b)\r\n{\r\n  auto lambda1 =\r\n   [=, lifetime = this-&gt;shared_from_this()]()\r\n   -&gt; Concurrency::task&lt;void&gt;\r\n  {\r\n   GetReady(a);\r\n   co_await GetSetAsync(b);\r\n   Go(a, b);\r\n  };\r\n\r\n  auto lambda2 = [](auto lambda1)\r\n   -&gt; Concurrency::task&lt;void&gt;\r\n  {\r\n   co_await lambda1();\r\n  };\r\n\r\n  lambda2(lambda1);\r\n}\r\n<\/pre>\n<p>The first lambda is the one we wish we could use. It captures anything it wants, and can <code>co_await<\/code> for whatever it likes. To ensure that <code>this<\/code> doesn&#8217;t disappear out from under it, we use the trick of <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190104-00\/?p=100635\"> capturing an explicit lifetime object into the lambda<\/a> to keep the parent object alive for the duration of the lambda.<\/p>\n<p>The second lambda is the one that captures nothing, and is therefore suitable for running as a fire-and-forget coroutine. It accepts the first lambda as its formal parameter, and since formal parameters are part of the frame, this keeps that copy alive for the duration of the task. We then invoke that first lambda with a <code>co_await<\/code> so that the second lambda&#8217;s task will not complete until the first lambda&#8217;s task is finished. This keeps the first lambda alive for the duration of the task.<\/p>\n<p>Finally, we invoke the second lambda, with the first lambda as its parameter, to set things into motion.<\/p>\n<p>The rest of the work is fine-tuning.<\/p>\n<p>We pass the first lambda by value, but that is a bit wasteful because the first lambda isn&#8217;t used any more once it is passed to the second lambda. Let&#8217;s move it rather than copying. Moving also allows the first lambda to capture move-only objects, like RAII types such as <code>std::unique_ptr<\/code>.<\/p>\n<pre>void MyClass::MyEventHandler(int a, int b)\r\n{\r\n  auto lambda1 =\r\n   [=, lifetime = this-&gt;shared_from_this()]()\r\n   -&gt; Concurrency::task&lt;void&gt;\r\n  {\r\n   GetReady(a);\r\n   co_await GetSetAsync(b);\r\n   Go(a, b);\r\n  };\r\n\r\n  auto lambda2 = [](auto lambda1)\r\n   -&gt; Concurrency::task&lt;void&gt;\r\n  {\r\n   co_await lambda1();\r\n  };\r\n\r\n  lambda2(<span style=\"color: blue;\">std::move(lambda1)<\/span>);\r\n}\r\n<\/pre>\n<p>But we can get the same effect as an explicit move by simply passing the second lambda inline, which creates an rvalue reference.<\/p>\n<pre>void MyClass::MyEventHandler(int a, int b)\r\n{\r\n  auto lambda2 = [](auto lambda1)\r\n   -&gt; Concurrency::task&lt;void&gt;\r\n  {\r\n   co_await lambda1();\r\n  };\r\n\r\n  lambda2(<span style=\"color: blue;\">[=, lifetime = this-&gt;shared_from_this()]()\r\n   -&gt; Concurrency::task&lt;void&gt;\r\n  {\r\n   GetReady(a);\r\n   co_await GetSetAsync(b);\r\n   Go(a, b);\r\n  }<\/span>);\r\n}\r\n<\/pre>\n<p>And then you can go all the way and make <code>lambda2<\/code> an immediately-invoked lambda.<\/p>\n<pre>void MyClass::MyEventHandler(int a, int b)\r\n{\r\n  [](auto lambda1)\r\n   -&gt; Concurrency::task&lt;void&gt;\r\n  {\r\n   co_await lambda1();\r\n  }(<span style=\"color: blue;\">[=, lifetime = this-&gt;shared_from_this()]()\r\n   -&gt; Concurrency::task&lt;void&gt;\r\n  {\r\n   GetReady(a);\r\n   co_await GetSetAsync(b);\r\n   Go(a, b);\r\n  }<\/span>);\r\n}\r\n<\/pre>\n<p>Next time, we&#8217;ll see what we can do to reduce the boilerplate needed to carry out this pattern.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>There are two types of lambdas you have to write, so why not write two lambdas?<\/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-100725","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>There are two types of lambdas you have to write, so why not write two lambdas?<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/100725","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=100725"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/100725\/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=100725"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=100725"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=100725"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}