{"id":103232,"date":"2019-12-20T07:00:00","date_gmt":"2019-12-20T15:00:00","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/oldnewthing\/?p=103232"},"modified":"2020-01-03T07:04:02","modified_gmt":"2020-01-03T15:04:02","slug":"20191220-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20191220-00\/?p=103232","title":{"rendered":"C++ coroutines: The problem of the synchronous apartment-changing callback"},"content":{"rendered":"<p>Today is a puzzle you can you can try to solve with the information you&#8217;ve learned about C++ coroutines and C++\/WinRT.<\/p>\n<p>C++\/WinRT uses the <code>IContext\u00adCallback<\/code> interface to <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20191129-00\/?p=103162\"> remember the context that initiated a <code>co_await<\/code> operation<\/a>, so it can resume execution in the original apartment when the <code>co_await<\/code> completes.<\/p>\n<p>The basic idea goes like this:<\/p>\n<pre>void await_suspend(std::experimental::coroutine_handle&lt;&gt; handle)\r\n{\r\n    async.Completed([handle,\r\n                     <span style=\"color: blue;\">context = CaptureCurrentApartmentContext()<\/span>]\r\n                    (auto const&amp;, Windows::Foundation::AsyncStatus)\r\n    {\r\n        \/\/ When the operation completes, get back to the\r\n        \/\/ original apartment and resume the coroutine there.\r\n        check_hresult(<a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20191128-00\/?p=103157\">InvokeInContext<\/a>(context.get(), handle));\r\n    });\r\n}\r\n<\/pre>\n<p>Maybe you see a problem here. I noticed a problem when I studied the C++\/WinRT code and meant to do a write-up on it eventually, but then I actually ran into the problem and <a href=\"https:\/\/github.com\/microsoft\/xlang\/issues\/544\"> alerted Kenny<\/a>, who promptly <a href=\"https:\/\/github.com\/microsoft\/xlang\/pull\/546\"> fixed it<\/a>. (Note: Clicking through gives away the answer.)<\/p>\n<p>The <code>IContext\u00adCallback::<\/code><code>Context\u00adCallback<\/code> method invokes the callback synchronously, and the invoking apartment is stuck waiting for the result. This is good if you want to callback to do some work that you are waiting for, but it&#8217;s not good if the caller just wants to fire and forget.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Thread 1<\/th>\n<td style=\"width: 1em;\" rowspan=\"6\">\u00a0<\/td>\n<th>Thread 2<\/th>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px black;\"><code>co_await something;<\/code><br \/>\n<code>await_suspend();<\/code><\/td>\n<\/tr>\n<tr>\n<td colspan=\"2\">operation continues<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-bottom: none;\">operation completes<br \/>\n<code>InvokeInContext<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px black;\"><code>handle();<\/code><br \/>\ncoroutine runs to next<br \/>\n\u2003suspension point<\/td>\n<td style=\"border: 1px black; border-style: none solid; text-align: center; vertical-align: middle; background-color: #ccc; background-image: repeating-linear-gradient(45deg,                   #ccc, #ccc 10px, white 10px, white 20px);\">blocked in<br \/>\nInvokeInContext<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-top: none;\"><code>InvokeInContext<\/code> returns<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In the above diagram, code inside a box represents code being executed on behavior of a coroutine. If the thread does not have a box, then it is available to do other work.<\/p>\n<p>A synchronous callback means that when this awaiter tries to resume execution, the thread that raised the <code>Completed<\/code> event is stuck until the continued coroutine reaches a suspension point or completes, because those are the things that cause the coroutine to return at the ABI. This period of time is represented by the shaded section labeled &#8220;blocked in Invoke\u00adIn\u00adContext&#8221;. During this period, the thread is not available to do work.<\/p>\n<p>This shaded period during which the thread is unresponsive may last for a long time. And that&#8217;s a problem if it&#8217;s a UI thread.<\/p>\n<p>Consider the following scenario:<\/p>\n<pre>IAsyncAction SomethingAsync()\r\n{\r\n    co_await resume_background();\r\n\r\n    DoBackgroundWork();\r\n\r\n    \/\/ Get to our UI thread so we can update UI.\r\n    co_await resume_foreground(Dispatcher());\r\n\r\n    UpdateUIStuff();\r\n\r\n    co_return;\r\n}\r\n<\/pre>\n<p>This coroutine switches immediately to a background thread, does a bunch of work, and then switches back to the UI thread to update some UI.<\/p>\n<p>You might decide to use this function like this:<\/p>\n<pre>IAsyncAction SomethingMoreAsync()\r\n{\r\n    \/\/ Do all our work on a background thread.\r\n    co_await resume_background();\r\n\r\n    LongBlockingOperation();\r\n    co_await SomethingAsync();\r\n    LongBlockingOperation();\r\n}\r\n<\/pre>\n<p>In C++\/WinRT, <code>co_await<\/code> of an <code>IAsync\u00adAction<\/code> returns control to the same apartment that originated the operation, so all of the <code>Long\u00adBlocking\u00adOperation<\/code> calls occur on a background thread. Certainly it&#8217;s safe to perform long blocking operations on a background thread, right?<\/p>\n<p>Let&#8217;s look more closely at what happens.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>UI thread<\/th>\n<td style=\"width: 1em;\" rowspan=\"61\">\u00a0<\/td>\n<th>Background thread<\/th>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px black;\"><code>SomethingMoreAsync<\/code> begins<br \/>\n<code>co_await resume_background();<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-bottom: none;\"><code>SomethingMoreAsync<\/code> resumes<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; height: 3em; background-color: #ddd;\"><code>LongBlockingOperation()<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-top: none;\"><code>SomethingAsync<\/code> begins<br \/>\n<code>co_await resume_background();<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"height: 1em;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-bottom: none;\"><code>SomethingAsync<\/code> resumes<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; height: 3em; background-color: #ddd;\"><code>DoBackgroundWork()<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-top: none;\"><code>co_await resume_foreground();<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px black; border-bottom: none;\"><code>SomethingAsync<\/code> resumes<br \/>\n<code>UpdateUIStuff();<\/code><br \/>\n<code>SomethingAsync<\/code> completes<br \/>\n<code>Completed()<\/code> handler tries to<br \/>\n\u2003resume on original context<br \/>\n<code>InvokeInContext<\/code><\/td>\n<td valign=\"bottom\"><span style=\"color: red;\">\u21d0<\/span> things get interesting<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px black; border-top: none; text-align: center; vertical-align: middle; background-color: #ccc; background-image: repeating-linear-gradient(45deg,                   #ccc, #ccc 10px, white 10px, white 20px);\" rowspan=\"3\">blocked in<br \/>\nInvokeInContext<\/td>\n<td style=\"border: solid 1px black; border-bottom: none;\"><code>handle();<\/code><br \/>\n<code>SomethingMoreAsync<\/code> resumes<\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px black; border-style: none solid; height: 3em; background-color: #ddd;\"><code>LongBlockingOperation()<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px black; border-top: none;\"><code>SomethingMoreAsync<\/code> completes<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The first part of the sequence goes as you would expect. The <code>Something\u00adMore\u00adAsync<\/code> coroutine moves to a background thread and performs a long blocking operation. This is okay, because we&#8217;re on a background thread.<\/p>\n<p>Next, it calls <code>Something\u00adAsync<\/code>, which starts by moving to a background thread. (It&#8217;s already on a background thread, but it doesn&#8217;t know that.)<\/p>\n<p>Once rescheduled (redundantly) on a background thread, it does some background work. Again, this background work can take a long time, but that&#8217;s okay because we&#8217;re on a background thread.<\/p>\n<p>When the background work is done, <code>Something\u00adAsync<\/code> moves back to the UI thread.<\/p>\n<p>Once back on the UI thread, <code>Something\u00adAsync<\/code> updates its UI and completes the coroutine.<\/p>\n<p>Now things get interesting.<\/p>\n<p>The awaiter for <code>IAsyncAction<\/code> wants to resume in the original apartment, which in this case means going back to a background thread. It does this by using <code>IContext\u00adCallback::<\/code><code>Context\u00adCallback<\/code>, which we wrapped inside <code>Invoke\u00adIn\u00adContext<\/code> for expository purposes.<\/p>\n<p>The <code>IContext\u00adCallback::<\/code><code>Context\u00adCallback<\/code> method invokes the callback synchronously, which means in our case that the call doesn&#8217;t return until the resumed coroutine reaches its next suspension point. But before it can complete or perform another <code>co_await<\/code>, it performs a long blocking operation, believing that since it is on a background thread, long blocking operations are permitted.<\/p>\n<p>And it&#8217;s true that long blocking operations are permitted on a background thread. The problem is that a UI thread is waiting for the background thread.<\/p>\n<p>The background thread is unwittingly holding up a UI thread.<\/p>\n<p><a href=\"https:\/\/github.com\/microsoft\/xlang\/pull\/546\"> The fix<\/a> is to use <code>IContext\u00adCallback::<\/code><code>Context\u00adCallback<\/code> only in the case when we need to return to a UI thread. If we need to return to a background thread, we can use the non-blocking <code>resume_<\/code><code>background<\/code> to do that.<\/p>\n<p>This means that if a background thread needs to return to a UI thread, then the background thread will be held hostage by the coroutine on the UI thread until it completes or suspends. That&#8217;s not so bad, because background threads can block. And besides, coroutines on UI threads are not supposed to perform long blocking operations in the first place.<\/p>\n<p>It also means that if a second UI thread needs to return to an originating UI thread, then the second UI thread will be held hostage by the coroutine on the originating UI thread until it completes or suspends. But that&#8217;s not so bad, because, as we noted before, coroutines on UI threads are not supposed to perform long blocking operations in the first place.<\/p>\n<p>Next time, we&#8217;ll look at a coroutine bug in the C++\/WinRT library and try to fix it by applying what we&#8217;ve learned so far.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Holding the caller thread hostage.<\/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-103232","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Holding the caller thread hostage.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103232","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=103232"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103232\/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=103232"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=103232"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=103232"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}