WEBKT

C++协程Promise对象深度解析:原理、成员函数与自定义实现

72 0 0 0

std::promise:协程通信的桥梁

std::promise的基本作用

为什么要用promise?

std::promise的成员函数:掌握核心操作

1. 构造函数

2. get_future()

3. set_value()

4. set_exception()

5. set_value_at_thread_exit() 和 set_exception_at_thread_exit()

6. reset()

7. swap()

自定义promise类型:更灵活的协程控制

1. promise类型的要求

2. 自定义promise类型的示例

3. 自定义promise类型的高级应用

std::promise与std::future的配合使用:构建完整的异步流程

总结:std::promise是协程控制的关键

C++20引入的协程为异步编程带来了极大的便利,而std::promise作为协程控制流中的重要一环,扮演着传递结果、处理异常的角色。今天,我们就来深入剖析std::promise,从它的基本概念、成员函数,到自定义promise类型以实现特定协程行为,全方位掌握这个关键组件。我会用更贴近实际开发的语言,结合代码示例,力求让你彻底搞懂promise的方方面面。

std::promise:协程通信的桥梁

在协程的世界里,std::promise就像一根管道,连接着协程的生产者(负责计算结果)和消费者(等待结果)。生产者通过promise设置结果或异常,而消费者则通过关联的std::future对象来获取这些信息。简单来说,promise负责设置,future负责获取。

std::promise的基本作用

  1. 传递协程结果:这是promise最基本的功能。协程计算完成后,可以将结果通过promise传递给等待的future对象。
  2. 传递异常:如果协程执行过程中发生异常,promise可以捕获并存储该异常,并通过future传递给调用者,实现异常处理。
  3. 同步协程状态promisefuture一起,可以用来同步协程的状态。future可以查询promise是否已经设置了结果或异常,从而判断协程是否已经完成。

为什么要用promise

你可能会问,为什么不直接使用全局变量或者其他方式来传递结果?使用promise的主要优势在于:

  • 线程安全std::promise的设计保证了在多线程环境下的线程安全。多个线程可以同时访问同一个promise对象,而不会出现数据竞争的问题。
  • 类型安全std::promise是模板类,可以指定传递的结果类型。这样可以在编译时检查类型错误,避免运行时出现意外的类型转换问题。
  • 异常处理std::promise可以方便地传递异常,使得协程的异常处理更加简单和可靠。
  • 解耦promise将协程的生产者和消费者解耦。生产者不需要知道哪个消费者在等待结果,只需要将结果设置到promise中即可。消费者也不需要知道哪个生产者在计算结果,只需要从future中获取结果即可。

std::promise的成员函数:掌握核心操作

要熟练使用std::promise,必须掌握它的常用成员函数。下面我们逐一介绍。

1. 构造函数

std::promise有多个构造函数,但最常用的是默认构造函数:

std::promise<int> p;

这将创建一个可以存储int类型结果的promise对象。你也可以使用移动构造函数来避免不必要的拷贝:

std::promise<int> p1;
std::promise<int> p2 = std::move(p1);

2. get_future()

get_future()函数用于获取与promise关联的std::future对象。这个future对象可以用来获取promise中存储的结果或异常。

std::promise<int> p;
std::future<int> f = p.get_future();

注意:每个promise对象只能调用一次get_future()函数。多次调用会抛出std::future_error异常。

3. set_value()

set_value()函数用于设置promise的结果值。一旦调用了set_value(),与该promise关联的future对象就可以获取到这个值。

std::promise<int> p;
std::future<int> f = p.get_future();
// 在另一个线程或协程中
p.set_value(42);
// 在主线程中
int result = f.get(); // result 将会是 42

4. set_exception()

set_exception()函数用于设置promise的异常。当协程执行过程中发生异常时,可以使用这个函数将异常传递给future对象。

std::promise<int> p;
std::future<int> f = p.get_future();
// 在另一个线程或协程中
try {
// ... 一些可能会抛出异常的代码
throw std::runtime_error("Something went wrong");
} catch (...) {
p.set_exception(std::current_exception());
}
// 在主线程中
try {
int result = f.get();
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}

std::current_exception()函数用于捕获当前异常,并将其封装成std::exception_ptr对象。future对象在调用get()函数时,如果发现promise中存储的是异常,会重新抛出该异常。

5. set_value_at_thread_exit()set_exception_at_thread_exit()

这两个函数与set_value()set_exception()类似,但它们会在当前线程退出时才设置promise的值或异常。这在某些情况下可以避免竞争条件,例如,当多个线程同时尝试设置同一个promise时。

6. reset()

reset()函数用于重置promise的状态,使其可以再次使用。调用reset()后,promise会释放之前存储的值或异常,并允许再次调用set_value()set_exception()。但是,你不能对已经调用过get_future()promise对象调用reset(),否则会抛出异常。

7. swap()

swap()函数用于交换两个promise对象的状态。这可以用来避免不必要的拷贝操作。

自定义promise类型:更灵活的协程控制

std::promise提供了基本的协程控制功能,但在某些情况下,我们需要更灵活的控制。这时,我们可以自定义promise类型,来实现特定的协程行为。自定义promise类型需要满足一些特定的要求,并实现一些特定的函数。

1. promise类型的要求

  • 必须是可移动构造和可移动赋值的:这是因为协程在挂起和恢复时,promise对象需要在不同的上下文之间传递。
  • 必须提供get_return_object()函数:这个函数用于返回与协程关联的coroutine_handle对象。coroutine_handle对象可以用来控制协程的执行。
  • 必须提供initial_suspend()函数:这个函数用于指定协程的初始挂起行为。通常返回std::suspend_alwaysstd::suspend_never
  • 必须提供final_suspend()函数:这个函数用于指定协程的最终挂起行为。通常返回std::suspend_alwaysstd::suspend_never
  • 必须提供unhandled_exception()函数:这个函数用于处理协程中未捕获的异常。
  • 可以提供return_void()函数:这个函数在协程返回void时被调用。
  • 可以提供return_value()函数:这个函数在协程返回一个值时被调用。

2. 自定义promise类型的示例

下面是一个简单的自定义promise类型的示例,它实现了基本的协程控制功能:

#include <iostream>
#include <coroutine>
#include <stdexcept>
struct MyPromise {
int result;
std::coroutine_handle<> handle;
MyPromise() : result(0), handle(nullptr) {}
~MyPromise() {
if (handle) {
handle.destroy();
}
}
auto get_return_object() {
return std::coroutine_handle<MyPromise>::from_promise(*this);
}
std::suspend_never initial_suspend() noexcept {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
void unhandled_exception() {
std::cerr << "Unhandled exception in coroutine" << std::endl;
std::terminate();
}
void return_value(int value) {
result = value;
}
void return_void() {}
};
struct MyCoroutine {
using promise_type = MyPromise;
std::coroutine_handle<MyPromise> handle;
MyCoroutine(std::coroutine_handle<MyPromise> h) : handle(h) {}
~MyCoroutine() {
if (handle) handle.destroy();
}
MyCoroutine(const MyCoroutine&) = delete;
MyCoroutine& operator=(const MyCoroutine&) = delete;
MyCoroutine(MyCoroutine&& other) noexcept : handle(other.handle) {
other.handle = nullptr;
}
MyCoroutine& operator=(MyCoroutine&& other) noexcept {
if (this != &other) {
if (handle) handle.destroy();
handle = other.handle;
other.handle = nullptr;
}
return *this;
}
int get_result() {
return handle.promise().result;
}
};
MyCoroutine my_coroutine() {
co_return 42;
}
int main() {
MyCoroutine coro = my_coroutine();
std::cout << "Result: " << coro.get_result() << std::endl; // 输出: Result: 42
}

这个示例定义了一个名为MyPromise的自定义promise类型,并实现了一些基本的函数。然后,定义了一个返回MyCoroutine的协程函数my_coroutine()。在main()函数中,我们调用my_coroutine()函数,并获取协程的结果。

3. 自定义promise类型的高级应用

自定义promise类型可以用于实现更高级的协程控制功能,例如:

  • 实现自定义的挂起和恢复行为:你可以通过自定义initial_suspend()final_suspend()函数来实现自定义的挂起和恢复行为。例如,你可以让协程在满足特定条件时才挂起或恢复。
  • 实现自定义的异常处理:你可以通过自定义unhandled_exception()函数来实现自定义的异常处理。例如,你可以将异常记录到日志文件中,或者将异常发送到远程服务器。
  • 实现自定义的结果传递:你可以通过自定义return_value()函数来实现自定义的结果传递。例如,你可以将结果存储到数据库中,或者将结果发送到消息队列中。

std::promisestd::future的配合使用:构建完整的异步流程

std::promise通常与std::future一起使用,以构建完整的异步流程。promise负责设置结果或异常,而future负责获取这些信息。下面是一个简单的示例,展示了如何使用promisefuture来实现异步计算:

#include <iostream>
#include <future>
#include <thread>
int calculate_sum(int a, int b, std::promise<int> p) {
try {
int sum = a + b;
p.set_value(sum);
return sum;
} catch (...) {
p.set_exception(std::current_exception());
return -1; // 或者其他错误码
}
}
int main() {
std::promise<int> p;
std::future<int> f = p.get_future();
std::thread t(calculate_sum, 10, 20, std::move(p));
try {
int result = f.get();
std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
t.join();
return 0;
}

在这个示例中,calculate_sum()函数在一个单独的线程中执行,它计算两个整数的和,并将结果通过promise传递给future对象。main()函数在主线程中等待future对象的结果,并打印结果或异常信息。

总结:std::promise是协程控制的关键

std::promise是C++协程中一个非常重要的组件,它负责传递协程的结果和异常,并同步协程的状态。通过掌握std::promise的成员函数和自定义promise类型,我们可以实现更灵活的协程控制,构建更强大的异步应用程序。希望通过本文的详细讲解,你已经对std::promise有了更深入的理解。现在,尝试在你的协程代码中使用promise,体验它带来的便利吧!记住,实践是检验真理的唯一标准!

AsyncMaster C++协程Promise对象

评论点评

打赏赞助
sponsor

感谢您的支持让我们更好的前行

分享

QRcode

https://www.webkt.com/article/9312