C++/WinRT 是微软为 Windows 运行时提供的现代 C++17 语言投影。
2018 年引入。Windows SDK 版本 10.0.17134.0(Windows 10 1803)。
本文不介绍C++ WinRT其他只介绍它提供的异步能力:
异步操作方法
假设我有一个耗时很久的方法,我不希望它阻塞UI线程,那你就可以这样做:
cpp
#include <dispatcherqueue.h>
#include <winrt/base.h>
#include <winrt/Windows.System.h>
#include <winrt/Windows.Foundation.h>
winrt::Windows::Foundation::IAsyncAction readImg()
{
co_await winrt::resume_background();
//这里省略了耗时很长的逻辑代码
co_await winrt::resume_foreground(dq);
}
这里,winrt::Windows::Foundation::IAsyncAction 代表一个无结果,无进度的异步操作。
除此之外,WinRT提供了如下异步操作类型
| 类型 | 返回值 | 进度报告 | 使用场景 |
|---|---|---|---|
IAsyncAction |
无 | 无 | 简单异步操作 |
IAsyncActionWithProgress<T> |
无 | 有 | 需要进度反馈的操作 |
IAsyncOperation<T> |
有 | 无 | 返回结果的异步操作 |
IAsyncOperationWithProgress<T, P> |
有 | 有 | 返回结果且需进度的操作 |
如果你要在一个方法中使用 co_await 操作异步逻辑,那么你就要把这个方法标记为 co_await 兼容方法,比如返回 IAsyncAction 类型。
co_await winrt::resume_background() 是 C++/WinRT 提供的线程切换辅助函数,用于从当前线程切换到后台线程(背后有池线程支持)。这个操作是非阻塞的。
co_await winrt::resume_foreground(dq) 负责从后台线程切换到当前线程。
是不是比C++标准库里的异步操作要简单的多呢?
dq是什么?
线程任务调度对象
winrt::Windows::System::DispatcherQueue dq 是 C++/WinRT 中用于管理线程任务调度的核心类,它管理按优先级排列的任务队列,确保任务在线程上以串行方式执行。这个类型的对象有以下特点:
- 每个线程最多拥有一个 DispatcherQueue
- 可以向该线程投递异步任务
- 任务按优先级排队执行
- 依赖线程消息循环(Message Loop)
在应用启动的时候,我们就创建了这个dq对象,它管理着UI线程的任务队列
cpp
winrt::Windows::System::DispatcherQueue dq;
dq{ winrt::Windows::System::DispatcherQueue::GetForCurrentThread() }
在WinUI3或者UWP项目中,通过上述代码就可以直接得到dq。
传统的Win32项目,必须要提前执行如下逻辑,才能得到dq。(DispatcherQueue 不是 Win32 线程的默认组成部分)
cpp
winrt::init_apartment(winrt::apartment_type::single_threaded);
DispatcherQueueOptions options{ sizeof(DispatcherQueueOptions),DQTYPE_THREAD_CURRENT,DQTAT_COM_STA };
static winrt::Windows::System::DispatcherQueueController controller{ nullptr };
auto hr = CreateDispatcherQueueController(options,
reinterpret_cast<ABI::Windows::System::IDispatcherQueueController**>(winrt::put_abi(controller)));
不要掐死UI线程
假设我们有如下异步代码,用于获取剪切板内的文本。
cpp
IAsyncOperation<winrt::hstring> Util::getTextFromClipboard()
{
auto view = Clipboard::GetContent();
if (view.Contains(StandardDataFormats::Text()))
{
co_return co_await view.GetTextAsync();
}
co_return L"";
}
如果你通过如下方法获得剪切板内的文本
cpp
auto str = getTextFromClipboard().get();
此时可能会报如下错误:

这是因为:
- .get() 阻塞当前线程等待异步完成
- 异步操作内部需要调度到UI线程执行 Clipboard API(操作剪切板的API的要求)
- 但当前UI线程被你阻塞了(.get() 没返回)
- 消息循环不运转,无法调度
- WinRT 检测到"该UI线程无法响应" 则抛出 !is_sta_thread
解决方案就是不要掐死UI线程
cpp
auto strOp = Util::getTextFromClipboard2();
strOp.Completed([this](auto const& sender, auto status) {
auto str = sender.GetResults();
});
或者把这段逻辑也包在IAsyncAction方法中,使用co_await调用getTextFromClipboard方法。