使用 C++/WinRT 执行并发和异步操作

2024-01-14 16:44

本文主要是介绍使用 C++/WinRT 执行并发和异步操作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本主题介绍协同例程和 co_await 的概念,我们建议你在 UI 应用程序和非 UI 应用程序中使用它们。

为了简单起见,本介绍主题中的大多数代码示例演示了 Windows 控制台应用程序 (C++/WinRT) 项目。 本主题中后面的代码示例使用协同例程,但为方便起见,控制台应用程序示例还会在退出前继续使用阻止性的 get 函数调用,这样应用程序就不会在显示其输出之前退出。 不要通过 UI 线程这样做(调用阻止性的 get 函数), 而应使用 co_await 语句。 高级并发和异步主题介绍了将要在 UI 应用程序中使用的技术。

本简介性主题介绍了可通过 C++/WinRT 创建和使用 Windows 运行时异步对象的部分方式。 阅读本主题后,如需其他技术,尤其是将要在 UI 应用程序中使用的技术,另请参阅高级并发和异步。

异步操作和 Windows 运行时“Async”函数

有可能需要超过 50 毫秒才能完成的任何 Windows 运行时 API 将实现为异步函数(具有一个以“Async”结尾的名称)。 异步函数的实现会启动另一线程上的工作,并且会立即返回表示异步操作的对象。 在异步操作完成后,返回的对象会包含从该工作中生成的任何值。 Windows::Foundation Windows 运行时命名空间包含四种类型的异步操作对象。

  • IAsyncAction;
  • IAsyncActionWithProgress<TProgress>;
  • IAsyncOperation<TResult>;
  • IAsyncOperationWithProgress<TResult, TProgress>。

每种异步操作类型都将投影到 winrt::Windows::Foundation C++/WinRT 命名空间中的相应类型。 C++/WinRT 还包含内部 await 适配器结构。 不要直接使用它,但借助该结构,可以编写 co_await 语句以协作等待返回其中一种异步操作类型的任何函数的结果。 然后,可以自行创作返回这些类型的协同例程。

异步 Windows 函数的示例是 SyndicationClient::RetrieveFeedAsync,其返回类型 IAsyncOperationWithProgress<TResult, TProgress> 的异步操作对象。


让我们来看一些阻止和不阻止使用 C++/WinRT 来调用类似 API 的方法。 我们将在接下来的几个代码示例中使用 Windows 控制台应用程序 (C++/WinRT) 项目,只为说明基本的概念。 更适用于 UI 应用程序的技术在高级并发和异步中讨论。

阻塞调用线程

以下代码示例接收来自 RetrieveFeedAsync 的异步操作对象,并且在该对象上调用 get 以阻塞调用线程,直到异步操作的结果可用。

若要将此示例直接复制并粘贴到 Windows 控制台应用程序 (C++/WinRT) 项目的主源代码文件中,请先在项目属性中设置“不使用预编译的标头”。

// main.cpp
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Syndication.h>using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;void ProcessFeed()
{Uri rssFeedUri{ L"https://blogs.windows.com/feed" };SyndicationClient syndicationClient;SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };// use syndicationFeed.
}int main()
{winrt::init_apartment();ProcessFeed();
}

调用 get 可以方便编写代码,对于出于任何原因不想使用协同例程的控制台应用或后台线程来说,这是一种理想选择。 但这既不是并发也不是异步操作,因此不适合 UI 线程(如果试图在 UI 线程上使用它,会在未优化的版本中触发断言)。 为了避免占用 OS 线程执行其他有用的工作,我们需要另一种方法。

编写协同例程

C++/WinRT 将 C++ 协同例程集成到编程模型中以提供协作等待结果的自然方式。 可以通过编写协同例程来生成自己的 Windows 运行时异步操作。 在以下代码示例中,ProcessFeedAsync 是协同例程。

get 函数位于 C++/WinRT 投影类型 winrt::Windows::Foundation::IAsyncAction 中,因此你可以从任意 C++/WinRT 项目内部调用该函数。 你将找不到列为 IAsyncAction 接口成员的函数,因为 get 不属于实际 Windows 运行时类型 IAsyncAction 的应用程序二进制接口 (ABI) 设计面。

// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;void PrintFeed(SyndicationFeed const& syndicationFeed)
{for (SyndicationItem const& syndicationItem : syndicationFeed.Items()){std::wcout << syndicationItem.Title().Text().c_str() << std::endl;}
}IAsyncAction ProcessFeedAsync()
{Uri rssFeedUri{ L"https://blogs.windows.com/feed" };SyndicationClient syndicationClient;SyndicationFeed syndicationFeed{ co_await syndicationClient.RetrieveFeedAsync(rssFeedUri) };PrintFeed(syndicationFeed);
}int main()
{winrt::init_apartment();auto processOp{ ProcessFeedAsync() };// do other work while the feed is being printed.processOp.get(); // no more work to do; call get() so that we see the printout before the application exits.
}

协同例程是可以暂停和恢复的函数。 在上述 ProcessFeedAsync 协同例程中,当达到 co_await 语句时,该协同例程会异步启动 RetrieveFeedAsync 调用,然后立即暂停自身并将控件返回到调用方(上述示例中为 main)。 然后,main 可以继续执行工作,同时将检索并打印提要。 完成该操作(RetrieveFeedAsync 调用完成)后,ProcessFeedAsync 协同例程将在下一个语句中恢复。

可以将一个协同例程聚合到其他协同例程中。 或者,也可以调用 get 以阻塞和等待其完成(以及获得结果,如果有)。 或者,可以将其传递到支持 Windows 运行时的其他编程语言。

也可以通过使用委托来处理异步操作的已完成和/或正在进行中的事件。 有关详细信息和代码示例,请参阅异步操作的委托类型。

正如你所看到的,在上面的代码示例中,我们在退出 main 之前继续使用阻止性的 get 函数调用。 但是,这只是为了让应用程序不会在显示其输出之前退出。

异步返回 Windows 运行时类型

在下一个示例中,我们将针对特定 URI 封装对 RetrieveFeedAsync 的调用,以为我们提供异步返回 SyndicationFeed 的 RetrieveBlogFeedAsync 函数。

// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;void PrintFeed(SyndicationFeed const& syndicationFeed)
{for (SyndicationItem const& syndicationItem : syndicationFeed.Items()){std::wcout << syndicationItem.Title().Text().c_str() << std::endl;}
}IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> RetrieveBlogFeedAsync()
{Uri rssFeedUri{ L"https://blogs.windows.com/feed" };SyndicationClient syndicationClient;return syndicationClient.RetrieveFeedAsync(rssFeedUri);
}int main()
{winrt::init_apartment();auto feedOp{ RetrieveBlogFeedAsync() };// do other work.PrintFeed(feedOp.get());
}

在上述示例中,RetrieveBlogFeedAsync 返回 IAsyncOperationWithProgress,其具有进度值和返回值。 我们可以在 RetrieveBlogFeedAsync 执行其操作并检索提要的同时进行其他工作。 然后,在该异步操作对象上调用 get,以阻塞、等待其完成,然后获取该操作的结果。

如果要异步返回 Windows 运行时类型,则应返回 IAsyncOperation<TResult> 或 IAsyncOperationWithProgress<TResult, TProgress>。 任何第一方或第三方运行时类或可以传入/传出 Windows 运行时函数的任何类型(例如 int 或 winrt::hstring)都符合条件。 如果尝试对非 Windows 运行时类型使用其中一种异步操作类型,编译器可帮助你处理“T 必须为 WinRT 类型”错误。

如果协同例程没有至少一条 co_await 语句,则为了符合成为协同例程的资格,它必须至少有一条 co_return 或一条 co_yield 语句。 在某些情况下,协同例程可以返回值而不引入任何异步,因此不阻塞也不切换上下文。 下面是一个通过缓存值来实现上述功能(第二次及后续调用时)的示例。

winrt::hstring m_cache;IAsyncOperation<winrt::hstring> ReadAsync()
{if (m_cache.empty()){// Asynchronously download and cache the string.}co_return m_cache;
}

异步返回非 Windows 运行时类型

如果要异步返回非 Windows 运行时类型的类型,则应返回并行模式库 (PPL) concurrency::task。 建议使用 concurrency::task,因为它将提供比 std::future 更好的性能(以及更好的兼容性)。

如果包含 <pplawait.h>,则可以使用 concurrency::task 作为协同例程类型。

// main.cpp
#include <iostream>
#include <ppltasks.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;concurrency::task<std::wstring> RetrieveFirstTitleAsync()
{return concurrency::create_task([]{Uri rssFeedUri{ L"https://blogs.windows.com/feed" };SyndicationClient syndicationClient;SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };return std::wstring{ syndicationFeed.Items().GetAt(0).Title().Text() };});
}int main()
{winrt::init_apartment();auto firstTitleOp{ RetrieveFirstTitleAsync() };// Do other work here.std::wcout << firstTitleOp.get() << std::endl;
}

参数传递

对于同步函数,默认情况下应该使用 const& 参数。 这将避免复制开销(涉及引用计数,意味着互锁的增加和减少)。

// Synchronous function.
void DoWork(Param const& value);

但如果向协同例程传递引用参数,可能会遇到问题。

// NOT the recommended way to pass a value to a coroutine!
IASyncAction DoWorkAsync(Param const& value)
{// While it's ok to access value here...co_await DoOtherWorkAsync(); // (this is the first suspension point)...// ...accessing value here carries no guarantees of safety.
}

在协同程序中,在第一个暂停点之前,执行是同步的;到达第一个暂停点时,控制返回到调用方,调用帧超出范围。 在协同例程恢复时,引用参数引用的源值可能已发生更改。 从协同例程的角度来看,引用参数具有不受控制的生命周期。 因此,在上面的示例中,在 co_await 之前,我们可以安全地访问 value,但之后就无法保证安全了。 如果调用方销毁了 value,则尝试在协同例程中访问它会导致内存损坏。 如果 DoOtherWorkAsync 函数有可能暂停并在恢复后尝试使用 value,我们也无法安全地将 value 传递给 DoOtherWorkAsync。

为了能够在暂停和恢复后安全地使用参数,默认情况下,协同例程应使用按值传递,以确保按值进行捕获并避免生命周期问题。 确信不遵从该指引也能安全进行操作的情况是很少见的。

// Coroutine
IASyncAction DoWorkAsync(Param value); // not const&

按值传递要求参数的移动或复制开销不高,智能指针通常就是这样的。

传递 const 值是否是一个好的做法也还存在争议(除非你想移动值)。 它不会对要复制的源值产生任何影响,但有助于表明意图,并避免你无意间修改副本。

// coroutine with strictly unnecessary const (but arguably good practice).
IASyncAction DoWorkAsync(Param const value);

另请参阅标准数组和向量,其中介绍了如何将标准向量传递到异步被调用方。

如果不能更改协同例程的签名,但是能够更改实现,则可在首次执行 co_await 之前进行本地复制。

IASyncAction DoWorkAsync(Param const& value)
{auto safe_value = value;// It's ok to access both safe_value and value here.co_await DoOtherWorkAsync();// It's ok to access only safe_value here (not value).
}

如果 Param 复制起来开销很大,则在首次执行 co_await 之前只提取所需的片段。

IASyncAction DoWorkAsync(Param const& value)
{auto safe_data = value.data;// It's ok to access safe_data, value.data, and value here.co_await DoOtherWorkAsync();// It's ok to access only safe_data here (not value.data, nor value).
}

在类成员协同例程中安全访问 this 指针

请参阅 C++/WinRT 中的强引用和弱引用。

这篇关于使用 C++/WinRT 执行并发和异步操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/605827

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名