{"id":107749,"date":"2023-01-25T07:00:00","date_gmt":"2023-01-25T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107749"},"modified":"2023-01-24T20:30:58","modified_gmt":"2023-01-25T04:30:58","slug":"20230125-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230125-00\/?p=107749","title":{"rendered":"Inside C++\/WinRT: Apartment switching: Unblocking the outgoing thread"},"content":{"rendered":"<p>Last time, <a title=\"Inside C++\/WinRT: Apartment switching: The basic idea\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230124-00\/?p=107746\"> we built an <code>apartment_context<\/code> object<\/a> and used it as part of our coroutine infrastructure so that <code>co_await<\/code> of Windows Runtime asynchronous operations resume in the same COM context as they started.<\/p>\n<p>Our implementation of the <code>apartment_<wbr \/>context<\/code> suffers from <a title=\"C++ coroutines: The problem of the synchronous apartment-changing callback\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20191220-00\/?p=103232\"> the problem of the synchronous apartment-changing callback<\/a>: The thread being switched <i>from<\/i> waits synchronously for the thread being switched <i>to<\/i>. This is a bad thing if the thread being switched <i>from<\/i> is a UI thread that needs to keep the UI responsive, whereas the thread being switched <i>to<\/i> is a background thread that is happy to make long blocking calls.<\/p>\n<p>C++\/WinRT addresses this problem by hopping through a background thread in cases where it thinks this will be a problem. In order to detect whether we are in a problem case, <a title=\"What do the output values from CoGetApartmentType mean?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20180208-00\/?p=97986\"> we use the <code>Co\u00adGet\u00adApartment\u00adType<\/code> function<\/a> to determine what kind of apartment we are in.<\/p>\n<pre>inline std::pair&lt;int32_t, int32_t&gt; get_apartment_type() noexcept\r\n{\r\n    int32_t aptType;\r\n    int32_t aptTypeQualifier;\r\n    check_hresult(WINRT_IMPL_CoGetApartmentType(&amp;aptType, &amp;aptTypeQualifier));\r\n    return { aptType, aptTypeQualifier };\r\n}\r\n\r\ninline bool is_sta_thread() noexcept\r\n{\r\n    auto type = get_apartment_type();\r\n    switch (type.first)\r\n    {\r\n    case 0: \/* APTTYPE_STA *\/\r\n    case 3: \/* APTTYPE_MAINSTA *\/\r\n        return true;\r\n    case 2: \/* APTTYPE_NA *\/\r\n        return type.second == 3 \/* APTTYPEQUALIFIER_NA_ON_STA *\/ ||\r\n            type.second == 5 \/* APTTYPEQUALIFIER_NA_ON_MAINSTA *\/;\r\n    }\r\n    return false;\r\n}\r\n<\/pre>\n<p>The code that switches apartments now checks whether it is on a thread that hosts a single-threaded apartment (STA). If so, then instead of resuming the coroutine synchronously via <code>Context\u00adCallback()<\/code>, it schedules the work to a background thread. That way, it is a background thread that blocks on a potentially long-running coroutine rather than a UI thread.<\/p>\n<p>First, we rename our old <code>resume_<wbr \/>apartment<\/code> function so it represents the <i>synchronous<\/i> resumption of the coroutine in another apartment.<\/p>\n<pre>void <span style=\"color: #08f;\">resume_apartment_sync<\/span>(\r\n    com_ptr&lt;IContextCallback&gt; const&amp; context,\r\n    std::coroutine_handle&lt;&gt; handle)\r\n{\r\n    com_callback_args args{};\r\n    args.data = handle.address();\r\n\r\n    check_hresult(\r\n        context-&gt;ContextCallback(resume_apartment_callback,\r\n            &amp;args,\r\n            guid_of&lt;ICallbackWithNoReentrancyToApplicationSTA&gt;(),\r\n            5, nullptr));\r\n}\r\n<\/pre>\n<p>And then we write a new <code>resume_<wbr \/>apartment<\/code> that resumes either synchronously or asynchronously, depending on the apartment context.<\/p>\n<pre>inline auto resume_apartment(\r\n    com_ptr&lt;IContextCallback&gt; const&amp; context,\r\n    coroutine_handle&lt;&gt; handle)\r\n{\r\n    WINRT_ASSERT(context.valid());\r\n    if (is_sta_thread())\r\n    {\r\n        resume_apartment_on_threadpool(context, handle);\r\n    }\r\n    else\r\n    {\r\n        resume_apartment_sync(context, handle);\r\n    }\r\n}\r\n<\/pre>\n<p>To resume a coroutine in an apartment from the threadpool, we do it in two steps. First, schedule work on the threadpool. The work consists of synchronously switching to the destination apartment for resuming the coroutine.<\/p>\n<pre>struct threadpool_resume\r\n{\r\n    threadpool_resume(com_ptr&lt;IContextCallback&gt; const&amp; context,\r\n        coroutine_handle&lt;&gt; handle) :\r\n        m_context(context), m_handle(handle) { }\r\n    com_ptr&lt;IContextCallback&gt; m_context;\r\n    coroutine_handle&lt;&gt; m_handle;\r\n};\r\n\r\ninline void __stdcall fallback_submit_threadpool_callback(\r\n    void*, void* p) noexcept\r\n{\r\n    std::unique_ptr&lt;threadpool_resume&gt;\r\n        state{ static_cast&lt;threadpool_resume*&gt;(p) };\r\n    resume_apartment_sync(state-&gt;m_context, state-&gt;m_handle);\r\n}\r\n\r\ninline void resume_apartment_on_threadpool(\r\n    com_ptr&lt;IContextCallback&gt; const&amp; context,\r\n    coroutine_handle&lt;&gt; handle)\r\n{\r\n    auto state = std::make_unique&lt;threadpool_resume&gt;(context, handle);\r\n    submit_threadpool_callback(fallback_submit_threadpool_callback,\r\n                               state.get());\r\n    state.release();\r\n}\r\n<\/pre>\n<p>The <code>resume_<wbr \/>apartment_<wbr \/>on_<wbr \/>threadpool<\/code> function captures its parameters into a <code>threadpool_<wbr \/>resume<\/code> structure and uses that pointer as the context pointer for a threadpool callback. The threadpool callback reconstitutes the <code>unique_<wbr \/>ptr<\/code> and uses <code>resume_<wbr \/>apartment_<wbr \/>sync<\/code> to resume the coroutine inside the apartment.<\/p>\n<p>We have avoided the problem of the synchronous apartment-changing callback blocking a UI thread for an extended period of time. We detected the dangerous situation and moved the work to a threadpool thread, which is not a UI thread.<\/p>\n<p>Next time, we&#8217;ll fix another problem with this implementation.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Avoiding the problem of the synchronous apartment-changing callback: Let the outgoing thread do whatever it wants to do next.<\/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-107749","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Avoiding the problem of the synchronous apartment-changing callback: Let the outgoing thread do whatever it wants to do next.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107749","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=107749"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107749\/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=107749"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107749"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107749"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}