C++20图片下载器工具项目实战

大纲:

  • 1. Awaitable 是什么?

  • 2. Awaiter 是什么?

  • 3. Awaitable 和 Awaiter 的关系

  • 4.图片下载工具实战

在 C++20 的协程(coroutines)框架中,Awaiter 和 Awaitable 是实现异步操作和协程暂停/恢复机制的核心概念。它们在协程的执行流程中扮演重要角色,尤其是在使用 co_await 关键字时。以下是对这两者的详细解析,并通过编写一个图片下载工具来深入理解。

注:懒人版,本节代码已更新至星球


1. Awaitable 是什么?

Awaitable 是一个广义的概念,指可以被 co_await 操作符暂停的对象。它通常是一个类型,定义了协程暂停和恢复的逻辑。Awaitable 本身并不是一个具体的类或接口,而是通过特定的协议(protocol)与协程框架交互。

Awaitable 的核心要求

一个类型要成为 Awaitable,必须满足以下条件(通过成员函数或自由函数实现):

  1. await_ready():检查异步操作是否已经完成。如果返回 true,协程不会暂停,直接继续执行;如果返回 false,协程将暂停。

  2. await_suspend():在协程暂停时调用,负责保存状态或注册回调。通常返回 voidbool 或 std::coroutine_handle,以控制协程的调度。

  3. await_resume():在协程恢复时调用,返回 co_await 表达式的结果。

这些函数可以通过以下方式定义:

  • 作为 Awaitable 类型的成员函数。

  • 作为自由函数(通过 ADL,Argument-Dependent Lookup)。

Awaitable 示例

以下是一个简单的 Awaitable 类型,用于模拟异步延迟:

#include <coroutine>
#include <chrono>
#include <thread>
#include <iostream>

struct Delay {
    int milliseconds;

    bool await_ready() const noexcept { returnfalse; } // 总是需要暂停

    void await_suspend(std::coroutine_handle<> handle) const noexcept {
        // 模拟异步延迟
        std::thread([handle, ms = milliseconds]() {
            std::this_thread::sleep_for(std::chrono::milliseconds(ms));
            handle.resume(); // 恢复协程
        }).detach();
    }

    void await_resume() const noexcept {
        std::cout << "Resumed after delay\n";
    }
};

这个 Delay 类型是一个 Awaitable,可以被 co_await 使用。


2. Awaiter 是什么?

Awaiter 是 Awaitable 的具体实现,或者说是 co_await 表达式求值后实际执行暂停/恢复逻辑的对象。换句话说,Awaitable 是提供 Awaiter 的类型,而 Awaiter 是包含 await_readyawait_suspend 和 await_resume 方法的实体。

Awaiter 的来源

当你使用 co_await expr

  1. 编译器首先评估 expr 得到一个 Awaitable 对象。

  2. 如果 Awaitable 本身满足 Awaiter 协议(即有 await_ready 等方法),它直接作为 Awaiter。

  3. 否则,编译器会尝试调用 Awaitable 的 operator co_await()(成员或自由函数)来获取一个 Awaiter。

Awaiter 示例

扩展上面的 Delay 示例,假设我们希望 Awaitable 和 Awaiter 分离:

struct DelayAwaiter {
    int milliseconds;

    bool await_ready() const noexcept { returnfalse; }

    void await_suspend(std::coroutine_handle<> handle) const noexcept {
        std::thread([handle, ms = milliseconds]() {
            std::this_thread::sleep_for(std::chrono::milliseconds(ms));
            handle.resume();
        }).detach();
    }

    void await_resume() const noexcept {
        std::cout << "Resumed after delay\n";
    }
};

struct Delay {
    int milliseconds;

    DelayAwaiter operator co_await() const noexcept {
        return DelayAwaiter{milliseconds};
    }
};

在这里,Delay 是 Awaitable,它通过 operator co_await 返回一个 DelayAwaiter(即 Awaiter)。


3. Awaitable 和 Awaiter 的关系

  • Awaitable 是用户面对的接口,通常是一个更高层次的类型,封装了异步操作的逻辑。

  • Awaiter 是底层实现,包含具体的暂停和恢复逻辑。

  • 转换过程

    • co_await expr 中的 expr 求值为 Awaitable。

    • 如果 Awaitable 不是 Awaiter,编译器调用 operator co_await() 获取 Awaiter。

    • Awaiter 的三个方法(await_readyawait_suspendawait_resume)控制协程的行为。


4.图片下载工具实战

  • 前提条件

支持C++20的gcc/clang,安装了libcurl。

核心功能实现如下:

  • 协程function功能

// 1.co_await
HttpRequest req{url, &response_data};
std::string result = co_await req; // 等待 HTTP 请求完成

// 2.保存文件
out_file.write(result.data(), result.size());
  • 实现Awaitable:HTTP 请求对象

// Awaitable:HTTP 请求对象
struct HttpRequest {
    std::string url;
    std::string* response_data;

    HttpRequest(const std::string& url, std::string* data) : url(url), response_data(data) {}

    HttpRequestAwaiter operator co_await() const noexcept {
        return HttpRequestAwaiter{url, response_data};
    }
};
  • 实现Awaiter

struct HttpRequestAwaiter {
   bool await_ready() const noexcept { returnfalse; } // 总是异步执行

    void await_suspend(std::coroutine_handle<> handle) noexcept {
        // 在单独线程中执行 HTTP 请求
        std::thread([this, handle]() {
            res = curl_easy_perform(curl);
            if (res != CURLE_OK) {
                std::cerr << "CURL error: " << curl_easy_strerror(res) << "\n";
            }
            handle.resume(); // 恢复协程
        }).detach();
    }

    std::string await_resume() const {
        if (res != CURLE_OK) {
            throwstd::runtime_error("HTTP request failed: " + std::string(curl_easy_strerror(res)));
        }
        return *response_data; // 返回响应数据
    }
};

最终的效果:


玩转cpp小项目星球3周年了!

学习更多干货,欢迎关注转发!

图片

图片

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐