C++ `std::coroutine_handle`:协程句柄的创建与操作

好的,没问题!我们现在开始进入协程句柄的世界!

各位观众,晚上好!今天我们要聊聊C++协程里的一个关键角色——std::coroutine_handle,也就是协程句柄。这玩意儿,初听起来感觉很高大上,但其实理解起来并不难,就像…就像剥洋葱,一层一层地扒开,总能看到核心的美味(虽然有些人觉得剥洋葱会辣眼睛)。

什么是协程句柄?

简单来说,std::coroutine_handle 是一个指向协程实例的指针。它允许你在协程外部控制协程的生命周期,比如恢复执行、销毁协程等等。你可以把它想象成一个遥控器,用来控制你的协程机器人。

为什么我们需要协程句柄?

因为协程不像普通函数那样,调用完就彻底结束了。协程可以挂起,可以恢复,可以在不同的时间点执行不同的代码。如果没有一个句柄来追踪和控制它,那简直就是一场灾难。想象一下,你的协程像脱缰的野马一样到处乱跑,你却无能为力,这感觉酸爽吗?

std::coroutine_handle 的类型

std::coroutine_handle 本身是一个模板类,可以接受一个模板参数,用来指定协程返回类型。

  • std::coroutine_handle<>:这是一个通用的协程句柄,可以指向任何类型的协程。
  • std::coroutine_handle<PromiseType>:这是一个指定了Promise类型的协程句柄。Promise类型是协程返回类型中的一个成员,它定义了协程的行为。

创建 std::coroutine_handle

创建协程句柄通常发生在协程返回对象的 promise().get_return_object() 函数中。这个函数负责返回一个包含协程句柄的对象,这个对象通常是 std::coroutine_tokenstd::coroutine_future 或其他自定义类型。

让我们看一个简单的例子:

#include <iostream>
#include <coroutine>

struct MyCoroutine {
    struct promise_type {
        int value;
        MyCoroutine get_return_object() {
            return {std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        void return_void() {}
    };

    std::coroutine_handle<promise_type> handle;

    MyCoroutine(std::coroutine_handle<promise_type> h) : handle(h) {}

    ~MyCoroutine() {
        if (handle) handle.destroy();
    }
};

MyCoroutine my_coroutine() {
    std::cout << "Coroutine startedn";
    co_await std::suspend_always{};
    std::cout << "Coroutine resumedn";
}

int main() {
    MyCoroutine coro = my_coroutine();
    coro.handle.resume();
    std::cout << "Main function resumedn";
    return 0;
}

在这个例子中:

  1. 我们定义了一个 MyCoroutine 结构体,它包含一个 std::coroutine_handle<promise_type> 类型的成员 handle
  2. MyCoroutine::promise_type 定义了协程的 Promise 类型,其中 get_return_object() 函数返回一个 MyCoroutine 对象,该对象包含了协程的句柄。
  3. my_coroutine() 函数是一个协程,它返回 MyCoroutine 对象。
  4. main() 函数中,我们调用 my_coroutine() 创建一个协程,并使用 coro.handle.resume() 恢复协程的执行。

std::coroutine_handle 的常用操作

std::coroutine_handle 提供了一些重要的操作,让我们来逐一介绍:

  • resume():恢复协程执行

    这是最常用的操作之一。resume() 函数会恢复协程的执行,让它从上次挂起的地方继续运行。就像按下了机器人的启动按钮,让它开始工作。

    std::coroutine_handle<> handle = ...; // 获取协程句柄
    handle.resume(); // 恢复协程执行
  • destroy():销毁协程

    destroy() 函数会销毁协程实例,释放它所占用的内存。在使用完协程后,一定要记得调用 destroy(),否则会造成内存泄漏。这就像用完的机器人,要记得关机并回收,不然会浪费电。

    std::coroutine_handle<> handle = ...; // 获取协程句柄
    handle.destroy(); // 销毁协程
  • done():检查协程是否完成

    done() 函数会返回一个布尔值,指示协程是否已经完成。如果协程已经执行到 final_suspend() 点或者抛出了未处理的异常,done() 就会返回 true

    std::coroutine_handle<> handle = ...; // 获取协程句柄
    if (handle.done()) {
        std::cout << "Coroutine is donen";
    } else {
        std::cout << "Coroutine is still runningn";
    }
  • address():获取协程实例的地址

    address() 函数返回协程实例的内存地址。这个地址可以用来进行一些底层的操作,比如调试。

    std::coroutine_handle<> handle = ...; // 获取协程句柄
    void* addr = handle.address(); // 获取协程实例的地址
  • *`from_address(void addr)`:从地址创建协程句柄**

    这是一个静态成员函数,可以从一个内存地址创建一个协程句柄。这个函数通常用于一些特殊的场景,比如从外部存储恢复协程的状态。

    void* addr = ...; // 协程实例的地址
    std::coroutine_handle<> handle = std::coroutine_handle<>::from_address(addr); // 从地址创建协程句柄
  • from_promise(PromiseType& promise):从 Promise 对象创建协程句柄

    这是一个静态成员函数,可以从一个 Promise 对象创建一个协程句柄。这个函数通常在协程返回对象的 promise().get_return_object() 函数中使用。

    struct MyCoroutine {
        struct promise_type {
            MyCoroutine get_return_object() {
                return {std::coroutine_handle<promise_type>::from_promise(*this)};
            }
            ...
        };
        ...
    };

一个更完整的例子

让我们来看一个更完整的例子,它演示了如何使用 std::coroutine_handle 来控制协程的生命周期:

#include <iostream>
#include <coroutine>

struct Task {
    struct promise_type {
        int result;

        Task get_return_object() {
            return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        void return_value(int value) { result = value; }
    };

    std::coroutine_handle<promise_type> handle;

    Task(std::coroutine_handle<promise_type> h) : handle(h) {}

    ~Task() {
        if (handle) handle.destroy();
    }

    int get_result() {
        return handle.promise().result;
    }
};

Task my_task() {
    std::cout << "Task startedn";
    co_await std::suspend_always{};
    std::cout << "Task resumedn";
    co_return 42;
}

int main() {
    Task task = my_task();
    std::cout << "Main function resumedn";
    task.handle.resume();
    std::cout << "Task result: " << task.get_result() << std::endl;
    return 0;
}

在这个例子中:

  1. 我们定义了一个 Task 结构体,它包含一个 std::coroutine_handle<promise_type> 类型的成员 handle
  2. Task::promise_type 定义了协程的 Promise 类型,其中 get_return_object() 函数返回一个 Task 对象,该对象包含了协程的句柄。return_value() 函数用于设置协程的返回值。
  3. my_task() 函数是一个协程,它返回 Task 对象。
  4. main() 函数中,我们调用 my_task() 创建一个协程,并使用 task.handle.resume() 恢复协程的执行。最后,我们使用 task.get_result() 获取协程的返回值。

使用 std::coroutine_handle 的注意事项

  • 生命周期管理: 你需要小心管理协程的生命周期。确保在使用完协程后调用 destroy(),避免内存泄漏。
  • 线程安全: std::coroutine_handle 本身不是线程安全的。如果你需要在多个线程中使用同一个协程句柄,你需要自己进行同步。
  • 异常处理: 协程中抛出的未处理异常会导致程序终止。你需要在 promise_type 中提供 unhandled_exception() 函数来处理异常。

*std::coroutine_handle 与 `void` 的转换**

std::coroutine_handle 可以与 void* 进行转换,这在某些场景下非常有用。例如,你可以将协程句柄存储在外部存储中,然后在需要的时候从存储中恢复协程句柄。

// 将 std::coroutine_handle 转换为 void*
void* ptr = handle.address();

// 将 void* 转换为 std::coroutine_handle
std::coroutine_handle<> recovered_handle = std::coroutine_handle<>::from_address(ptr);

表格总结

操作 描述
resume() 恢复协程的执行。
destroy() 销毁协程实例,释放内存。
done() 检查协程是否完成。
address() 获取协程实例的地址。
from_address() 从地址创建协程句柄。
from_promise() 从 Promise 对象创建协程句柄。

高级用法:自定义 Promise 类型

std::coroutine_handle 的威力在于它可以与自定义的 Promise 类型一起使用。通过自定义 Promise 类型,你可以完全控制协程的行为。

例如,你可以自定义一个 Promise 类型,实现协程的取消功能:

#include <iostream>
#include <coroutine>
#include <atomic>

struct CancellableTask {
    struct promise_type {
        int result;
        std::atomic<bool> cancelled{false};

        CancellableTask get_return_object() {
            return {std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        void return_value(int value) { result = value; }

        auto await_transform(std::suspend_always) {
            struct Awaiter {
                promise_type* promise;
                bool await_ready() { return false; }
                void await_suspend(std::coroutine_handle<> h) {
                    if (promise->cancelled) {
                        h.destroy(); // 立即销毁协程
                    }
                }
                void await_resume() {}
            };
            return Awaiter{this};
        }
    };

    std::coroutine_handle<promise_type> handle;

    CancellableTask(std::coroutine_handle<promise_type> h) : handle(h) {}

    ~CancellableTask() {
        if (handle) handle.destroy();
    }

    int get_result() {
        return handle.promise().result;
    }

    void cancel() {
        handle.promise().cancelled = true;
        handle.resume(); // 尝试恢复协程,让 await_transform 执行
    }
};

CancellableTask my_cancellable_task() {
    std::cout << "Task startedn";
    co_await std::suspend_always{};
    std::cout << "Task resumedn";
    co_return 42;
}

int main() {
    CancellableTask task = my_cancellable_task();
    std::cout << "Main function resumedn";
    task.cancel(); // 取消协程
    std::cout << "Task cancelledn";
    return 0;
}

在这个例子中,我们在 Promise 类型中添加了一个 cancelled 标志,并在 await_transform 中检查这个标志。如果 cancelledtrue,我们就立即销毁协程。

总结

std::coroutine_handle 是 C++ 协程中一个非常重要的组成部分。它允许你控制协程的生命周期,恢复协程的执行,并获取协程的状态。通过自定义 Promise 类型,你可以实现更高级的协程功能,比如取消、超时等等。

希望今天的讲座能帮助你更好地理解 std::coroutine_handle。记住,协程的世界很广阔,还有很多值得探索的地方。继续学习,继续实践,你一定会成为协程大师!

感谢大家的观看,下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注