From 41e6fe77ae712ce7400862e9c007ebc5efa9c1f1 Mon Sep 17 00:00:00 2001 From: "Chris Guzak (WINDOWS)" Date: Sun, 12 Apr 2026 14:55:21 -0700 Subject: [PATCH] Add get_only_safe_from_non_presenting_sta() to async operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a peer to .get() on IAsyncAction, IAsyncOperation, IAsyncActionWithProgress, and IAsyncOperationWithProgress that skips the _DEBUG-only STA blocking assert. The existing .get() asserts !is_sta_thread() to guard against blocking UI threads. However, not all STAs are UI threads — some never present UI, haven't presented yet, or never will. The assert is also _DEBUG-only, making it invisible to codebases that don't build with _DEBUG (e.g. the Windows OS). The new method get_only_safe_from_non_presenting_sta() is functionally identical to .get() but omits the STA check. The intentionally long name communicates the risk to callers. Changes: - strings/base_coroutine_foundation.h: Add wait_get_bypass_sta_check() impl helper and get_only_safe_from_non_presenting_sta() for all 4 async consume templates - cppwinrt/code_writers.h: Add declaration to generated code for all 4 async types - test/test_nocoro: Add test calling the new method from an STA thread using a real WinRT async operation (PathIO::ReadTextAsync on C:\Windows\win.ini) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cppwinrt/code_writers.h | 4 ++++ strings/base_coroutine_foundation.h | 33 +++++++++++++++++++++++++++++ test/test_nocoro/get.cpp | 33 +++++++++++++++++++++++++++++ test/test_nocoro/pch.h | 1 + 4 files changed, 71 insertions(+) diff --git a/cppwinrt/code_writers.h b/cppwinrt/code_writers.h index fc5081bef..855c901bb 100644 --- a/cppwinrt/code_writers.h +++ b/cppwinrt/code_writers.h @@ -1433,24 +1433,28 @@ namespace cppwinrt else if (type_name == "Windows.Foundation.IAsyncAction") { w.write(R"( auto get() const; + auto get_only_safe_from_non_presenting_sta() const; auto wait_for(Windows::Foundation::TimeSpan const& timeout) const; )"); } else if (type_name == "Windows.Foundation.IAsyncOperation`1") { w.write(R"( auto get() const; + auto get_only_safe_from_non_presenting_sta() const; auto wait_for(Windows::Foundation::TimeSpan const& timeout) const; )"); } else if (type_name == "Windows.Foundation.IAsyncActionWithProgress`1") { w.write(R"( auto get() const; + auto get_only_safe_from_non_presenting_sta() const; auto wait_for(Windows::Foundation::TimeSpan const& timeout) const; )"); } else if (type_name == "Windows.Foundation.IAsyncOperationWithProgress`2") { w.write(R"( auto get() const; + auto get_only_safe_from_non_presenting_sta() const; auto wait_for(Windows::Foundation::TimeSpan const& timeout) const; )"); } diff --git a/strings/base_coroutine_foundation.h b/strings/base_coroutine_foundation.h index 4f1c8d0b6..23c414da1 100644 --- a/strings/base_coroutine_foundation.h +++ b/strings/base_coroutine_foundation.h @@ -99,6 +99,19 @@ namespace winrt::impl return async.GetResults(); } + template + auto wait_get_bypass_sta_check(Async const& async) + { + auto status = async.Status(); + if (status == Windows::Foundation::AsyncStatus::Started) + { + status = wait_for_completed(async, 0xFFFFFFFF); // INFINITE + } + check_status_canceled(status); + + return async.GetResults(); + } + #ifdef WINRT_IMPL_COROUTINES struct ignore_apartment_context {}; @@ -220,6 +233,11 @@ namespace winrt::impl impl::wait_get(static_cast(static_cast(*this))); } template + auto consume_Windows_Foundation_IAsyncAction::get_only_safe_from_non_presenting_sta() const + { + impl::wait_get_bypass_sta_check(static_cast(static_cast(*this))); + } + template auto consume_Windows_Foundation_IAsyncAction::wait_for(Windows::Foundation::TimeSpan const& timeout) const { return impl::wait_for(static_cast(static_cast(*this)), timeout); @@ -231,6 +249,11 @@ namespace winrt::impl return impl::wait_get(static_cast const&>(static_cast(*this))); } template + auto consume_Windows_Foundation_IAsyncOperation::get_only_safe_from_non_presenting_sta() const + { + return impl::wait_get_bypass_sta_check(static_cast const&>(static_cast(*this))); + } + template auto consume_Windows_Foundation_IAsyncOperation::wait_for(Windows::Foundation::TimeSpan const& timeout) const { return impl::wait_for(static_cast const&>(static_cast(*this)), timeout); @@ -242,6 +265,11 @@ namespace winrt::impl impl::wait_get(static_cast const&>(static_cast(*this))); } template + auto consume_Windows_Foundation_IAsyncActionWithProgress::get_only_safe_from_non_presenting_sta() const + { + impl::wait_get_bypass_sta_check(static_cast const&>(static_cast(*this))); + } + template auto consume_Windows_Foundation_IAsyncActionWithProgress::wait_for(Windows::Foundation::TimeSpan const& timeout) const { return impl::wait_for(static_cast const&>(static_cast(*this)), timeout); @@ -253,6 +281,11 @@ namespace winrt::impl return impl::wait_get(static_cast const&>(static_cast(*this))); } template + auto consume_Windows_Foundation_IAsyncOperationWithProgress::get_only_safe_from_non_presenting_sta() const + { + return impl::wait_get_bypass_sta_check(static_cast const&>(static_cast(*this))); + } + template auto consume_Windows_Foundation_IAsyncOperationWithProgress::wait_for(Windows::Foundation::TimeSpan const& timeout) const { return impl::wait_for(static_cast const&>(static_cast(*this)), timeout); diff --git a/test/test_nocoro/get.cpp b/test/test_nocoro/get.cpp index 11339e10b..5fa558e6a 100644 --- a/test/test_nocoro/get.cpp +++ b/test/test_nocoro/get.cpp @@ -2,6 +2,7 @@ using namespace winrt; using namespace Windows::Foundation; +using namespace Windows::Storage; template struct async_completion_source : implements, IAsyncOperation, IAsyncInfo> @@ -72,3 +73,35 @@ TEST_CASE("get") REQUIRE(acs.as>().get() == 0xDEADBEEF); } + +TEST_CASE("get_only_safe_from_non_presenting_sta") +{ + // Call a real WinRT async operation from an STA thread. + // This is the scenario the new API is designed for: an STA that is not + // presenting UI, where a synchronous blocking wait is safe. + std::exception_ptr failure{}; + std::thread sta_thread([&failure] + { + try + { + winrt::init_apartment(winrt::apartment_type::single_threaded); + + auto content = PathIO::ReadTextAsync(L"C:\\Windows\\win.ini").get_only_safe_from_non_presenting_sta(); + + REQUIRE(content.size() > 0); + + winrt::uninit_apartment(); + } + catch (...) + { + failure = std::current_exception(); + } + }); + + sta_thread.join(); + + if (failure) + { + std::rethrow_exception(failure); + } +} diff --git a/test/test_nocoro/pch.h b/test/test_nocoro/pch.h index 7ff48a37c..30a6d3079 100644 --- a/test/test_nocoro/pch.h +++ b/test/test_nocoro/pch.h @@ -2,5 +2,6 @@ #include "catch.hpp" #include "winrt/Windows.Foundation.h" +#include "winrt/Windows.Storage.h" using namespace std::literals;