当前位置: 首页 > news >正文

windows C++-并行编程-PPL任务并行(二)

延续任务

在异步编程中,一个异步操作在完成时调用另一个操作并将数据传递到其中的情况非常常见。 传统上,这使用回调方法来完成。 在并发运行时中,延续任务提供了同样的功能。 延续任务(也简称为“延续”)是一个异步任务,由另一个任务(称为先行)在完成时调用。 使用延续可以:

  • 将数据从前面的任务传递到延续;
  • 指定调用或不调用延续所依据的精确条件;
  • 在延续启动之前取消延续,或在延续正在运行时以协作方式取消延续;
  • 提供有关应如何计划延续的提示。 (这仅适用于通用 Windows 平台 (UWP) 应用;
  • 从同一前面的任务中调用多个延续;
  • 在多个先行任务中的全部或任意任务完成时调用一个延续;
  • 将延续依次相连,形成任意长度;
  • 使用延续来处理先行引发的异常;

这些功能使你可以在第一个任务完成时执行一个或多个任务。 例如,可以创建在第一个任务从磁盘读取文件之后压缩文件的延续。

下面的示例将上面的示例修改为使用 concurrency::task::then 方法来计划在先行任务的值可用时打印该值的延续。

// basic-continuation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>using namespace concurrency;
using namespace std;int wmain()
{auto t = create_task([]() -> int{return 42;});t.then([](int result){wcout << result << endl;}).wait();// Alternatively, you can chain the tasks directly and// eliminate the local variable./*create_task([]() -> int{return 42;}).then([](int result){wcout << result << endl;}).wait();*/
}/* Output:42
*/

可以按任意长度链接和嵌套任务。 一个任务还可以具有多个延续。 下面的示例演示将上一个任务的值增加三倍的基本延续链。

// continuation-chain.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>using namespace concurrency;
using namespace std;int wmain()
{auto t = create_task([]() -> int{ return 0;});// Create a lambda that increments its input value.auto increment = [](int n) { return n + 1; };// Run a chain of continuations and print the result.int result = t.then(increment).then(increment).then(increment).get();wcout << result << endl;
}/* Output:3
*/

延续还可以返回另一个任务。 如果没有取消,则此任务会在后续延续之前执行。 此技术称为异步解包。 要在后台执行其他工作,但不想当前任务阻止当前线程时,异步解包会很有用。 (这在 UWP 应用中很常见,其中延续可以在 UI 线程上运行)。 下面的示例演示三个任务。 第一个任务返回在延续任务之前运行的另一个任务。 

// async-unwrapping.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>using namespace concurrency;
using namespace std;int wmain()
{auto t = create_task([](){wcout << L"Task A" << endl;// Create an inner task that runs before any continuation// of the outer task.return create_task([](){wcout << L"Task B" << endl;});});// Run and wait for a continuation of the outer task.t.then([](){wcout << L"Task C" << endl;}).wait();
}/* Output:Task ATask BTask C
*/

当任务的延续返回 N 类型的嵌套任务时,生成的任务具有 N 类型(而不是 task<N>),会在嵌套任务完成时完成。 换句话说,延续会执行嵌套任务的解包。 

基于值的延续与基于任务的延续

对于其返回类型是 T 的 task 对象,可以向其延续任务提供 T 或 task<T> 类型的值。 采用类型 T 的延续称为基于值的延续。 基于值的延续计划在先行任务完成而未出现错误并且未取消时执行。 采用类型 task<T> 作为其参数的延续称为基于任务的延续。 基于任务的延续始终计划为在先行任务完成时执行,甚至是在先行任务取消或引发异常时执行。 随后然后调用 task::get 以获取先行任务的结果。 如果先行任务已取消,则 task::get 会引发 concurrency::task_canceled。 如果先行任务引发了异常,则 task::get 会再次引发该异常。 基于任务的延续在先行任务取消时不会标记为已取消。

when_all 函数

when_all 函数生成在任务集完成之后完成的任务。 此函数返回 std::vector 对象,其中包含集中每个任务的结果。 下面的基本示例使用 when_all 创建一个表示三个其他任务完成的任务。

// join-tasks.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>using namespace concurrency;
using namespace std;int wmain()
{// Start multiple tasks.array<task<void>, 3> tasks = {create_task([] { wcout << L"Hello from taskA." << endl; }),create_task([] { wcout << L"Hello from taskB." << endl; }),create_task([] { wcout << L"Hello from taskC." << endl; })};auto joinTask = when_all(begin(tasks), end(tasks));// Print a message from the joining thread.wcout << L"Hello from the joining thread." << endl;// Wait for the tasks to finish.joinTask.wait();
}/* Sample output:Hello from the joining thread.Hello from taskA.Hello from taskC.Hello from taskB.
*/

传递给 when_all 的任务必须统一。 换句话说,它们必须全部返回相同类型。

还可以使用 && 语法生成在任务集完成之后完成的任务,如下面的示例所示。

auto t = t1 && t2; // same as when_all

可将延续与 when_all 结合使用以在任务集完成之后执行操作,这十分常见。 下面的示例将上面的示例修改为打印各自生成 int 结果的三个任务的总和。

// Start multiple tasks.
array<task<int>, 3> tasks =
{create_task([]() -> int { return 88; }),create_task([]() -> int { return 42; }),create_task([]() -> int { return 99; })
};auto joinTask = when_all(begin(tasks), end(tasks)).then([](vector<int> results)
{wcout << L"The sum is " << accumulate(begin(results), end(results), 0)<< L'.' << endl;
});// Print a message from the joining thread.
wcout << L"Hello from the joining thread." << endl;// Wait for the tasks to finish.
joinTask.wait();/* Output:Hello from the joining thread.The sum is 229.
*/

在此示例中,还可以指定 task<vector<int>> 以生成基于任务的延续。

如果任务集中的任何任务取消或引发异常,则 when_all 会立即完成,不等待其余任务完成。 如果引发异常,则运行时会在你对 when_all 返回的任务对象调用 task::get 或 task::wait 时再次引发异常。 如果有多个任务引发,则运行时会选择其中之一。 因此,请确保在所有任务完成之后观察到所有异常;未经处理的任务异常会导致应用终止。

下面是可以用于确保程序观察到所有异常的实用工具函数。 对于处于提供的范围内的每个任务,observe_all_exceptions 会触发再次引发的任何异常,然后会吞并该异常。

// Observes all exceptions that occurred in all tasks in the given range.
template<class T, class InIt> 
void observe_all_exceptions(InIt first, InIt last) 
{std::for_each(first, last, [](concurrency::task<T> t){t.then([](concurrency::task<T> previousTask){try{previousTask.get();}// Although you could catch (...), this demonstrates how to catch specific exceptions. Your app// might handle different exception types in different ways.catch (Platform::Exception^){// Swallow the exception.}catch (const std::exception&){// Swallow the exception.}});});
}

请考虑一个使用 C++ 和 XAML 并将文件集写入磁盘的 UWP 应用。 下面的示例演示如何使用 when_all 和 observe_all_exceptions 确保该程序观察到所有异常。

// Writes content to files in the provided storage folder.
// The first element in each pair is the file name. The second element holds the file contents.
task<void> MainPage::WriteFilesAsync(StorageFolder^ folder, const vector<pair<String^, String^>>& fileContents)
{// For each file, create a task chain that creates the file and then writes content to it. Then add the task chain to a vector of tasks.vector<task<void>> tasks;for (auto fileContent : fileContents){auto fileName = fileContent.first;auto content = fileContent.second;// Create the file. The CreationCollisionOption::FailIfExists flag specifies to fail if the file already exists.tasks.emplace_back(create_task(folder->CreateFileAsync(fileName, CreationCollisionOption::FailIfExists)).then([content](StorageFile^ file){// Write its contents.return create_task(FileIO::WriteTextAsync(file, content));}));}// When all tasks finish, create a continuation task that observes any exceptions that occurred.return when_all(begin(tasks), end(tasks)).then([tasks](task<void> previousTask){task_status status = completed;try{status = previousTask.wait();}catch (COMException^ e){// We'll handle the specific errors below.}// TODO: If other exception types might happen, add catch handlers here.// Ensure that we observe all exceptions.observe_all_exceptions<void>(begin(tasks), end(tasks));// Cancel any continuations that occur after this task if any previous task was canceled.// Although cancellation is not part of this example, we recommend this pattern for cases that do.if (status == canceled){cancel_current_task();}});
}

 下面是这个例子的运行:


1. 在 MainPage.xaml 中,添加一个 Button 控件。<Button x:Name="Button1" Click="Button_Click">Write files</Button>2. 在 MainPage.xaml.h 中,将这些前向声明添加到 MainPage 类声明的 private 节。void Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
concurrency::task<void> WriteFilesAsync(Windows::Storage::StorageFolder^ folder, const std::vector<std::pair<Platform::String^, Platform::String^>>& fileContents);3. 在 MainPage.xaml.cpp 中,实现 Button_Click 事件处理程序。// A button click handler that demonstrates the scenario.
void MainPage::Button_Click(Object^ sender, RoutedEventArgs^ e)
{// In this example, the same file name is specified two times. WriteFilesAsync fails if one of the files already exists.vector<pair<String^, String^>> fileContents;fileContents.emplace_back(make_pair(ref new String(L"file1.txt"), ref new String(L"Contents of file 1")));fileContents.emplace_back(make_pair(ref new String(L"file2.txt"), ref new String(L"Contents of file 2")));fileContents.emplace_back(make_pair(ref new String(L"file1.txt"), ref new String(L"Contents of file 3")));Button1->IsEnabled = false; // Disable the button during the operation.WriteFilesAsync(ApplicationData::Current->TemporaryFolder, fileContents).then([this](task<void> previousTask){try{previousTask.get();}// Although cancellation is not part of this example, we recommend this pattern for cases that do.catch (const task_canceled&){// Your app might show a message to the user, or handle the error in some other way.}Button1->IsEnabled = true; // Enable the button.});
}4. 在 MainPage.xaml.cpp 中,实现 WriteFilesAsync,如示例所示。

when_all 是生成 task 作为其结果的的非阻止函数。 与 task::wait 不同,可以安全地在 UWP 应用中在 ASTA(应用程序 STA)线程上调用此函数。 

相关文章:

windows C++-并行编程-PPL任务并行(二)

延续任务 在异步编程中&#xff0c;一个异步操作在完成时调用另一个操作并将数据传递到其中的情况非常常见。 传统上&#xff0c;这使用回调方法来完成。 在并发运行时中&#xff0c;延续任务提供了同样的功能。 延续任务(也简称为“延续”)是一个异步任务&#xff0c;由另一个…...

快速了解 servlet(SpringMVC 的底层)

Servlet 是 Java EE&#xff08;现 Jakarta EE&#xff09;中用于处理 Web 请求的核心组件。它在 Web 应用程序的服务器端运行&#xff0c;负责接收和处理客户端&#xff08;如浏览器&#xff09;的请求&#xff0c;并生成响应。 尽管现代Web开发更多采用SpringMVC等框架&…...

QT中tr的作用是什么

在Qt框架中&#xff0c;tr() 函数是一个非常重要的宏&#xff0c;它用于国际化和本地化&#xff08;i18n和l10n&#xff09;支持。tr() 函数使得Qt应用程序能够根据不同的语言环境&#xff08;locale&#xff09;显示相应的翻译文本&#xff0c;从而支持多种语言。 具体来说&a…...

OpenCV结构分析与形状描述符(7)计算轮廓的面积的函数contourArea()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算轮廓的面积。 该函数计算轮廓的面积。与 moments 类似&#xff0c;面积是使用格林公式计算的。因此&#xff0c;返回的面积与你使用 drawCo…...

内网环境使用Docker部署Qwen2模型-vLLM篇

在此之前&#xff0c;我们已成功利用Docker与Ollama框架&#xff0c;在内网环境中部署了Qwen2模型。下面我们再来看一下使用Docker与vLLM框架部署Qwen2模型。 准备vLLM镜像 在一台具备网络环境的机器上执行以下命令&#xff0c;拉取vLLM的镜像&#xff1a; # 官方镜像 docke…...

Rust的常数、作用域与所有权

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust到底值不值得学&#xff0c;之一 -CSDN博客 Rust到底值不值得学&#xff0c;之二-CSDN博客 Rust的数据类型-CSDN博客 3.7 常…...

Spring 源码解读:解决循环依赖的三种方式

引言 在复杂的应用开发中&#xff0c;循环依赖是一个常见的问题。简单来说&#xff0c;循环依赖是指两个或多个Bean之间互相依赖&#xff0c;导致程序无法正常实例化这些Bean。Spring容器通过依赖注入&#xff08;DI&#xff09;来管理Bean的创建与生命周期&#xff0c;并在遇…...

Web3 详解

1. 使用 Web3 库 Web3 是一个 JavaScript 库&#xff0c;可用于通过 RPC 通信与以太坊节点通信。 Web3 的工作方式是&#xff0c;公开已通过 RPC 启用的方法&#xff0c;这允许开发利用 Web3 库的用户界面&#xff0c;以便与部署在区块链上的合约进行交互。 一旦 Geth JavaScri…...

Spring 中依赖注入注解的区别详解

一、依赖注入的基本概念 依赖注入是一种设计模式,通过将对象的依赖以参数的形式传入类中,而不是在类中自行创建依赖对象。这样做有几个好处: 降低耦合度:类与类之间的依赖关系变得更清晰,避免了硬编码依赖。提高可测试性:通过依赖注入,可以轻松地进行单元测试,因为可以…...

PTA求一批整数中出现最多的个位数字

作者 徐镜春 单位 浙江大学 给定一批整数&#xff0c;分析每个整数的每一位数字&#xff0c;求出现次数最多的个位数字。例如给定3个整数1234、2345、3456&#xff0c;其中出现最多次数的数字是3和4&#xff0c;均出现了3次。 输入格式&#xff1a; 输入在第1行中给出正整数…...

探索国产编程工具:如何实现工作效率翻倍

在当前软件开发领域&#xff0c;国产编程工具正在迅速发展&#xff0c;它们在功能、性能以及用户体验上都有显著提升&#xff0c;以下是一些国产编程工具&#xff0c;它们可以帮助开发者提升工作效率。 智能代码编辑器 CodeGeeX&#xff1a;这是一款由清华大学和智谱AI合作开…...

秒懂:进程相关的操作

1.进程的查看 1.1创建test.cc文件&#xff0c;运行以下代码 #include <stdio.h> #include <sys/types.h> #include <unistd.h>int main() {while(1){sleep(1);} return 0;}1.2 执行以下命令 1. 运行test.cc文件 并将其最终的可执行文件命名为 test gcc t…...

PDF 软件如何帮助您编辑、转换和保护文件。

如何找到最好的 PDF 编辑器。 无论您是在为您的企业寻找更高效的 PDF 解决方案&#xff0c;还是尝试组织和编辑主文档&#xff0c;PDF 编辑器都可以在一个地方提供您需要的所有工具。市面上有很多 PDF 编辑器 — 在决定哪个最适合您时&#xff0c;请考虑这些因素。 1. 确定您的…...

蓝桥杯嵌入式国三备赛经验分享

1 学习STM32入门视频 向大家推荐一套宝藏级别的视频&#xff1a;【STM32入门教程-2023版 细致讲解 中文字幕】 如果已经比过蓝桥杯单片机或学习过单片机相关课程的同学&#xff0c;你们可以尝试不需要STM32套件进行学习。如果没有学过单片机相关课程的同学&#xff0c;可以买…...

AI编程工具合集

1. 简介 1.1. 概述 AI编程,即人工智能编程,是编写用于创建智能系统(如机器学习模型、自然语言处理应用程序等)的代码的过程。AI编程涉及使用算法和数据结构来实现能够执行任务的程序,这些任务通常需要人类智能才能完成。 AI编程的基础是计算机科学原理,包括数据结构、…...

[网络编程]通过java用TCP实现网络编程

文章目录 一. 通过java用TCP实现网络编程api介绍代码实现上述代码存在的问题 一. 通过java用TCP实现网络编程 api介绍 1. ServerSocket ServerSocket是专门给服务器用的api 构造方法: 方法: 2. Socket 不管是客⼾端还是服务端Socket&#xff0c;都是双⽅建⽴连接以后&#…...

Python(TensorFlow)和Java及C++受激发射损耗导图

&#x1f3af;要点 神经网络监督去噪预测算法聚焦荧光团和检测模拟平台伪影消除算法性能优化方法自动化多尺度囊泡动力学成像生物研究多维分析统计物距粒子概率算法 Python和MATLAB图像降噪算法 消除噪声的一种方法是将原始图像与表示低通滤波器或平滑操作的掩模进行卷积。…...

IEEE投稿模板翻译

>将这一行替换为您的稿件id号(双击此处编辑)< IEEE 期刊和会议论文的撰写准备&#xff08;2022&#xff09; 第一作者 A. 作者&#xff0c;IEEE成员&#xff0c;第二作者 B. 作者&#xff0c;第三作者 C. 作者 Jr.&#xff0c;IEEE成员 摘要—本文档为IEEE会刊、期刊和…...

log4j 1.x 日志输出线程以唯一ID的形式配置

在 Log4j 1.x 中&#xff0c;直接以线程ID&#xff08;如Java中的Thread.currentThread().getId()返回的ID&#xff09;的形式记录日志是可行的&#xff0c;但 Log4j 1.x 本身并不直接提供一个内建的、自动将每个线程ID转换为“同一时间段内唯一ID”的机制。线程ID本身在JVM的上…...

宏观学习笔记:GDP分析(二)

GDP分析&#xff08;一&#xff09;主要是介绍GDP相关的定义以及核算逻辑&#xff0c;本节主要介绍GDP的分析思路。GDP分析主要是2种方法&#xff1a;总量分析和结构分析。 1. 总量分析 1.1 数值选择 一般情况下&#xff0c;分析的对象都是 官方公布的GDP当季值。 1.2 趋势规…...

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位&#xff1a;为1时表示在内存期间被访问过&#xff0c;为0时表示未被访问&#xff1b;修改位&#xff1a;为1时表示该页面自从被装入内存后被修改过&#xff0c;为0时表示未修改过。 置换页面时&#xff0c;最先置换访问位和修改位为…...

C高级编程 第十六天(树 二叉树)

1.树 1.1结构特点 非线性结构&#xff0c;有一个直接前驱&#xff0c;但可能有多个直接后继有递归性&#xff0c;树中还有树可以为空&#xff0c;即节点个数为零 1.2相关术语 根&#xff1a;即根结点&#xff0c;没有前驱叶子&#xff1a;即终端结点&#xff0c;没有后继森…...

OpenCV结构分析与形状描述符(11)椭圆拟合函数fitEllipse()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 围绕一组2D点拟合一个椭圆。 该函数计算出一个椭圆&#xff0c;该椭圆在最小二乘意义上最好地拟合一组2D点。它返回一个内切椭圆的旋转矩形。使…...

904.水果成篮

题目 链接&#xff1a;leetcode链接 思路分析&#xff08;滑动窗口&#xff09; 读完题目&#xff0c;很明显&#xff0c;这个题目需要我们寻找一个最长子数组&#xff0c;使得这个子数组里面最多存在两种不同的数字&#xff0c;很容易联想到使用滑动窗口。 另外&#xff…...

【网络安全】漏洞挖掘之 2FA 恢复代码安全措施不当

未经许可,不得转载。 文章目录 正文正文 目标:example.com 2024年6月,我在HackerOne上参与一个私人项目时发现了一个与2FA(双因素身份验证)恢复代码管理相关的安全漏洞。该漏洞发生在用户禁用并重新启用2FA的过程中。问题在于,系统在2FA重新启用后,仍然接受此前生成的…...

指令微调与参数微调的代码实践与分析

文章目录 指令微调的实验性分析LoRA 代码实践与分析指令微调的示例代码与预训练的代码高度一致,区别主要在于指令微调数据集的构建(SFTDataset)和序列到序列损失的计算(DataCollatorForSupervisedDataset)。以下代码展示了 LLMBox 和 YuLan-Chat 中指令微调的整体训练流程…...

Android14音频进阶之高通Elite架构指定通道播放(八十四)

简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+…...

常见的正则化方法以及L1,L2正则化的简单描述

深度学习中的正则化是通过在模型训练过程中引入某些技术来防止模型过拟合的一种策略。过拟合是指模型在训练数据上表现非常好&#xff0c;但在新的、未见过的数据上表现不佳。正则化通过限制模型的复杂度或对模型参数施加约束&#xff0c;从而提高模型的泛化能力。 常见的正则…...

深入理解 Milvus:新一代向量数据库的基础技术与实战指南

一、什么是 Milvus&#xff1f; Milvus 是一个开源的向量数据库&#xff0c;专门设计用于存储和检索大规模的高维向量数据。无论是图像、视频、音频还是文本&#xff0c;通过将这些数据转换为向量&#xff0c;Milvus 都能通过近似最近邻搜索&#xff08;Approximate Nearest N…...

Maven教程——从入门到入坑

第1章 为什么要使用Maven 1.1 获取第三方jar包   开发中需要使用到的jar包种类繁多&#xff0c;获取jar包的方式都不尽相同。为了查找一个jar包找遍互联网&#xff0c;身心俱疲。不仅如此&#xff0c;费劲心血找到的jar包里有的时候并没有你需要的那个类&#xff0c;又或者有…...