C++异步调用 future async promise packaged_task

背景:

C++ 异步调用是现代 C++ 编程中的一种重要技术,它允许程序在等待某个任务完成时继续执行其他代码,从而提高程序的效率和响应性。

C++11 引入了 std::async、std::future 和 std::promise 等工具,使得异步编程变得更加方便和直观。以下是关于 C++ 异步调用的详细介绍,包括基本概念、使用方法和示例代码。

以下代码头文件为:

#include <iostream>
#include <future>
#include <thread>
#include <chrono>

std::future:

std::future 是一个类模板,提供了一种访问异步操作结果的机制。

主要成员函数包括 get、wait 和 wait_for,其中 get 用于获取异步操作的结果,如果结果还不可用,get 会阻塞当前线程,直到结果可用。

注意

一般配合另外三个使用,否则发挥不出来效果

get只能调用一次多次调用抛异常

调用wait(仍然可以使用get()获取结果

wait_for()等待异步操作完成等待指定时间段如果超时返回timeout.

 

std::async:

std::async 是一个函数模板,用于启动异步任务。它返回一个 std::future 对象,可以用来获取异步任务的结果。

std::async 可以指定执行策略。例如 std::launch::async 表示在新线程中异步执行std::launch::deferred 表示延迟执行,直到调用 std::future::get 或 std::future::wait 时才开始执行

 代码:

int compute(int x) 
{
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    return x * x;
}

int asyncTest()
{
    // 启动异步任务
    std::future<int> result = std::async(std::launch::async, compute, 5);
    // 继续执行其他代码
    std::cout << "Doing some other work..." << std::endl;
    // 获取异步任务的结果
    int value = result.get(); // 这里会等待。
    std::cout << "Result: " << value << std::endl;
    return 0;
}

 

std::promise:

std::promise 是一个类模板,用于在异步任务中设置结果值。std::promise 可以与 std::future 配合使用,通过 std::promise::set_value 设置结果值,然后通过 std::future::get 获取结果值。

如何理解配合thread用来获取另外一个线程的执行结果

如图1)启动线程B时可以传一个菜篮子(就是promise)进去。2)当线程B干完可以菜篮子。3)线程A通过get获取菜篮子

注意事项

1.  std::promise的生命周期:确保std::promise对象在std:: future对象需要它之前保持有效。一旦std::promise对象被销毁,任何尝试通过std:: future对象访问其结果的操作都将失败。

2.  线程安全:std::promise的set_value和set_exception方法是线程安全的,但你应该避免在多个线程中同时调用它们,因为这通常意味着你的设计存在问题。

3.  异常处理:当使用std::promise时,要特别注意异常处理。如果std::promise的set_exception方法没有被调用,但异步操作中确实发生了异常,那么这些异常将不会被捕获,并可能导致程序崩溃。

4.  性能考虑:虽然std::promise和std::future提供了强大的异步编程能力,但它们也引入了额外的开销。在性能敏感的应用程序中,要仔细考虑是否真的需要它们。

5.  std::move 的使用:在将std::promise对象传递给线程函数时,通常需要使用std::move来避免不必要的复制。这是因为std::promise对象通常包含非托管资源(如共享状态),复制它们可能是昂贵的或不必要的。

 代码:

void doWork(std::promise<int>&& p) {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    p.set_value(42); // 设置结果值
}

int promiseTest() 
{
    std::promise<int> prom;  // 创建 promise
    std::future<int> fut = prom.get_future();

    // 启动线程执行异步任务.  注意这里一定要用move进去,不能用拷贝构造。
    std::thread t(doWork, std::move(prom));

    // 继续执行其他代码
    std::cout << "Doing some other work..." << std::endl;

    // 获取异步任务的结果
    int value = fut.get();
    std::cout << "Result: " << value << std::endl;

    t.join();
    return 0;
}

 

std::packaged_task:

std::packaged_task 是一个类模板,包装了一个可调用对象(如普通函数、lambda 表达式、函数对象等),以便异步调用。它也可以与 std::future 配合使用,通过 std::packaged_task::get_future 获取 std::future 对象。

就是一堆函数封装一下方便异步调用适合用来线程池不同任务封装成统一packaged_task然后统一调度参考https://www.cnblogs.com/xcywt/p/18429228

启动流程

 代码:

int computeTTT(int x) 
{
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    return x * x;
}

int packaged_task_Test() {
    // 创建 packaged_task
    std::packaged_task<int(int)> task(computeTTT);  // 把一个函数包装一下。
    std::future<int> result = task.get_future();

    // 启动线程执行异步任务
    std::thread t(std::move(task), 5);

    // 继续执行其他代码
    std::cout << "Doing some other work..." << std::endl;

    // 获取异步任务的结果
    int value = result.get();
    std::cout << "Result: " << value << std::endl;

    t.join();
    return 0;
}

 

比较:

async使用简单灵活性有限无法控制任务调度方式(比如在哪个线程启动)适用于简单异步任务不用复杂任务调度管理

promise需要手动管理任务运行结果传递一般配合thread使用代码管理复杂一点

packaged_task适合需要高度封装任务管理特别适合自定义线程池任务队列

 

 

 

posted @ 2024-10-14 18:36  xcywt  阅读(32)  评论(0编辑  收藏  举报
作者:xcywt
出处:https://www.cnblogs.com/xcywt//
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如果文中有什么错误,欢迎指出。以免更多的人被误导。