Skip to content

Task 与 Generator

TaskGeneratorkoroutine_lib 提供的两种核心协程类型。Task 用于表示一个最终会完成的异步操作,而 Generator 用于创建一个可以按需生成值序列的迭代器。

1. Task<T>: 异步任务

Task<T> 是库中最常用的组件。它代表一个可能正在进行中的计算,这个计算最终会产生一个 T 类型的结果,或者 void(如果 Tvoid),或者抛出异常。

基本用法

一个返回 Task<T> 的函数就是一个协程。你可以使用 co_return 来返回值。

#include "koroutine/koroutine.h"
#include <iostream>

// 一个简单的 Task,异步返回一个整数
Task<int> get_answer() {
    co_await sleep_for(std::chrono::seconds(1)); // 模拟耗时工作
    co_return 42;
}

// 一个无返回值的 Task
Task<void> print_message() {
    co_await sleep_for(std::chrono::milliseconds(500));
    std::cout << "Hello from a Task<void>!" << std::endl;
}

int main() {
    // 使用 Runtime::block_on 来同步等待并获取 Task 的结果
    int answer = Runtime::block_on(get_answer());
    std::cout << "The answer is: " << answer << std::endl;

    // 对于 Task<void>,Runtime::block_on 会等待它执行完毕
    Runtime::block_on(print_message());

    return 0;
}

链式调用与组合

Task 的强大之处在于其可组合性。你可以在一个协程中 co_await 另一个 Task,形成清晰的、线性的异步逻辑流。

// 异步获取用户ID
Task<int> get_user_id(const std::string& username) {
  std::cout << "Looking up user: " << username << std::endl;
  co_await sleep_for(std::chrono::milliseconds(100));
  co_return 42;
}

// 异步获取用户分数
Task<int> get_user_score(int user_id) {
  std::cout << "Getting score for user " << user_id << std::endl;
  co_await sleep_for(std::chrono::milliseconds(100));
  co_return user_id * 10;
}

// 将多个异步操作串联起来
Task<void> process_user(const std::string& username) {
    int user_id = co_await get_user_id(username);
    std::cout << "Got user ID: " << user_id << std::endl;

    int score = co_await get_user_score(user_id);
    std::cout << "Got score: " << score << std::endl;
}

int main() {
    Runtime::block_on(process_user("alice"));
}

这个例子中,process_user 看起来就像普通的同步代码,但实际上每个 co_await 都可能涉及一次挂起和恢复,完全不会阻塞主线程。

异常处理: .catching()

如果一个 Task 内部抛出异常,这个异常会被捕获并可以在调用链中处理。使用 .catching() 方法可以附加一个异常处理器。

Task<int> might_throw() {
    co_await sleep_for(std::chrono::milliseconds(100));
    throw std::runtime_error("Something went wrong!");
    co_return 0; // 不会执行到这里
}

int main() {
    auto task = might_throw();

    // 使用 .catching() 来处理异常
    auto handled_task = std::move(task).catching([](std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    });

    // Runtime::block_on 会执行任务,如果发生异常,处理器会被调用
    Runtime::block_on(std::move(handled_task));
}

清理操作: .finally()

.finally() 方法允许你附加一个无论 Task 成功还是失败都会执行的清理回调。这对于释放资源非常有用。

Task<void> work_with_resource() {
    std::cout << "Acquiring resource..." << std::endl;
    co_await sleep_for(std::chrono::milliseconds(200));
    std::cout << "Work done." << std::endl;
}

int main() {
    auto task = work_with_resource();

    auto final_task = std::move(task).finally([]() {
        std::cout << "Releasing resource..." << std::endl;
    });

    Runtime::block_on(std::move(final_task));
}

2. Generator<T>: 值序列生成器

Generator<T> 是一种特殊的协程,它不返回单个值,而是使用 co_yield 产生一个值的序列。它就像一个可以暂停和恢复的函数,每次恢复时都产生下一个值。

基本用法

#include "koroutine/generator.hpp"
#include <iostream>

// 一个生成从 start 到 end-1 的整数序列的 Generator
Generator<int> range(int start, int end) {
    for (int i = start; i < end; ++i) {
        std::cout << "(Yielding " << i << ")" << std::endl;
        co_yield i;
    }
}

int main() {
    auto numbers = range(0, 5);

    // 使用 has_next() 和 next() 遍历
    while (numbers.has_next()) {
        std::cout << "Got value: " << numbers.next() << std::endl;
    }
}

输出:

(Yielding 0)
Got value: 0
(Yielding 1)
Got value: 1
(Yielding 2)
Got value: 2
(Yielding 3)
Got value: 3
(Yielding 4)
Got value: 4

注意,range 函数中的代码和 main 函数中的循环是交错执行的。

链式操作

Generator 也支持函数式风格的链式操作,如 map, take 等。

Generator<int> numbers = range(0, 10);

// 创建一个新的 Generator,它只取前5个偶数并平方
auto result_generator = std::move(numbers)
    .take_while([](int i) { return i < 10; }) // 实际上这里可以省略,因为range只到10
    .filter([](int i) { return i % 2 == 0; }) // 假设有 filter
    .take(5)
    .map([](int i) { return i * i; });

while (result_generator.has_next()) {
    std::cout << result_generator.next() << " "; // 输出: 0 4 16 36 64
}