{"id":103660,"date":"2020-04-09T07:00:00","date_gmt":"2020-04-09T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=103660"},"modified":"2020-04-08T20:19:04","modified_gmt":"2020-04-09T03:19:04","slug":"20200409-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200409-00\/?p=103660","title":{"rendered":"Creating a non-agile delegate in C++\/WinRT, part 4: Waiting synchronously from a background thread"},"content":{"rendered":"<p><b>Warning to those who stumbled onto this page<\/b>: Don&#8217;t use the code on this page without reading all the way to the end.<\/p>\n<p>This week, we assembled a function <code>resume_synchronous<\/code> that synchronously resumes execution in another apartment. The use case for this is a a delegate running on a background thread that needs to run code synchronously on a UI thread.<\/p>\n<p>You can get the same effect using things that come built into C++\/WinRT: You can call the <code>get()<\/code> method on a Windows Runtime asynchronous operation, and C++\/WinRT will block the calling apartment until the asynchronous operation is complete.<\/p>\n<pre>winrt::IAsyncAction DoActualWorkAsync(\r\n    CoreDispatcher dispatcher, DeviceInformation info)\r\n{\r\n  co_await winrt::resume_foreground(dispatcher);\r\n  viewModel.Append(winrt::make&lt;DeviceItem&gt;(info));\r\n}\r\n\r\ndeviceWatcher.Added(\r\n    [=](auto&amp;&amp; sender, auto&amp;&amp; info)\r\n    {\r\n        DoActualWorkAsync(Dispatcher(), info).get();\r\n    });\r\n<\/pre>\n<p>The idea here is that we start by calling <code>Do\u00adActual\u00adWork\u00adAsync<\/code>, which does its work asynchronously. But instead of <code>co_await<\/code>ing the result, we <code>get()<\/code> it: The <code>get()<\/code> method waits synchronously for the operation to complete.<\/p>\n<p>Waiting synchronously for the asynchronous operation to complete is advisable only from background threads. Performing a synchronous wait from a UI thread will naturally make your program unresponsive for the duration of the wait. But it&#8217;s worse than that: The asynchronous operation may itself want to use the UI thread, but it won&#8217;t able to since you blocked it. The result is a deadlock on your UI thread, and that makes everybody sad.<\/p>\n<p>Now, splitting out the asynchronous part into a separate function is a bit of an annoyance, since everything you want to use on the UI thread needs to be passed in as a parameter. Maybe we can do better.<\/p>\n<pre>template&lt;typename TLambda&gt;\r\nwinrt::IAsyncAction DispatchAsync(\r\n    CoreDispatcher dispatcher, TLambda&amp;&amp; lambda)\r\n{\r\n  co_await winrt::resume_foreground(dispatcher);\r\n  lambda();\r\n}\r\n\r\ndeviceWatcher.Added(\r\n    [=](auto&amp;&amp; sender, auto&amp;&amp; info)\r\n    {\r\n        DispatchAsync(Dispatcher(), [&amp;]\r\n        {\r\n            viewModel.Append(winrt::make&lt;DeviceItem&gt;(info));\r\n        }).get();\r\n    });\r\n<\/pre>\n<p>This version accepts a lambda and runs it after switching to the dispatcher&#8217;s UI thread.<\/p>\n<p>But this code looks all wrong. We&#8217;re taking objects captured by reference and using them across a suspending <code>co_await<\/code> boundary! Isn&#8217;t this a recipe for disaster, using a reference after the referent may have been destroyed?<\/p>\n<p>Yes, this is dangerous in general, but it works in this specific case because the outer <code>IAsync\u00adAction<\/code> is not <code>co_await<\/code>ed; it is passed to <code>get()<\/code>, which performs a synchronous wait. Therefore, all the parameters will remain valid for the lifetime of the coroutine, because the destruction of the parameters doesn&#8217;t happen until the end of the &#8220;full expression&#8221; that ends in <code>get()<\/code>.<\/p>\n<p>Once we realize that, we can take things a step further:<\/p>\n<pre>deviceWatcher.Added(\r\n    [=](auto&amp;&amp; sender, auto&amp;&amp; info)\r\n    {\r\n        [&amp;]() -&gt; winrt::IAsyncAction\r\n        {\r\n            co_await winrt::resume_foreground(Dispatcher());\r\n            viewModel.Append(winrt::make&lt;DeviceItem&gt;(info));\r\n        }().get();\r\n    });\r\n<\/pre>\n<p>The lambda won&#8217;t be destructed until the end of the full expression, which happens after <code>get()<\/code> returns, which means that all the reference captures will remain valid for the lifetime of the lambda.<\/p>\n<p>I guess you could factor this:<\/p>\n<pre>template&lt;typename TLambda&gt;\r\nvoid RunSyncOnDispatcher(\r\n    CoreDispatcher const&amp; dispatcher,\r\n    TLambda&amp;&amp; lambda)\r\n{\r\n  [&amp;]() -&gt; winrt::IAsyncAction\r\n  {\r\n    co_await winrt::resume_foreground(dispatcher);\r\n    lambda();\r\n  }().get();\r\n}\r\n\r\ndeviceWatcher.Added(\r\n    [=](auto&amp;&amp; sender, auto&amp;&amp; info)\r\n    {\r\n        RunSyncOnDispatcher(Dispatcher(), [&amp;]()\r\n        {\r\n            viewModel.Append(winrt::make&lt;DeviceItem&gt;(info));\r\n        });\r\n    });\r\n<\/pre>\n<p>It does create the risk that somebody will pass a lambda that is itself a coroutine. Since <code>Run\u00adSync\u00adOn\u00adDispatcher<\/code> does not <code>co_await<\/code> the result of the lambda, the synchronous execution lasts only up until the lambda reaches its first suspending <code>co_await<\/code>, which makes the rest of the lambda coroutine execute without the protection of the <code>get()<\/code>.<\/p>\n<p>There&#8217;s a sneaky bug in this code, however. We&#8217;ll look at it next time.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Using things you already have.<\/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-103660","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Using things you already have.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103660","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=103660"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103660\/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=103660"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=103660"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=103660"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}