<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Topics tagged with c++20协程]]></title><description><![CDATA[A list of topics that have been tagged with c++20协程]]></description><link>http://forum.d2learn.org/tags/c++20协程</link><generator>RSS for Node</generator><lastBuildDate>Sun, 19 Apr 2026 09:12:48 GMT</lastBuildDate><atom:link href="http://forum.d2learn.org/tags/c++20协程.rss" rel="self" type="application/rss+xml"/><pubDate>Invalid Date</pubDate><ttl>60</ttl><item><title><![CDATA[【 基于 io_uring 的 C++20 协程网络库】01：基础骨架与 Awaiter 机制]]></title><description><![CDATA[<h3>目标与设计边界</h3>
<p dir="auto">本文旨在实现一个基于 <code>io_uring</code> 封装的 C++ 协程网络库。在着手编码前，我们先确立一个严格的设计边界：</p>
<p dir="auto"><strong>不考虑跨平台，不考虑兼容 epoll 等传统多路复用机制。</strong></p>
<p dir="auto">为什么舍弃跨平台等通用性？<br />
一旦引入跨平台封装，不仅维护成本陡增，更关键的是<strong>性能势必要做出妥协</strong>。不同操作系统的异步 API 在机制上存在根本分歧，强行封装通常只能取它们的公共子集，或者在用户态引入额外的抽象层来模拟缺失的语义。无论哪种方式，都会对最终性能造成不可预期的损耗。</p>
<p dir="auto">在基础设施级别的系统库中，性能是无法在项目后期通过“手法”来弥补的，必须在架构初期就定下基调。因此，我们选择不给自己埋雷，直接将底层与 <code>io_uring</code> 强绑定。</p>
<h3>为什么选择 io_uring？</h3>
<p dir="auto">相比于 epoll，<code>io_uring</code> 的心智模型更加契合协程。<br />
epoll 暴露的是 Reactor 模型接口（就绪通知），本质上依然是接近线程回调的处理方式。而 <code>io_uring</code> 是标准的 Proactor 模型（完成通知）。C++20 的协程天然就是一个异步操作状态机，也是标准的 Proactor 范式。两者的结合能最大程度地降低封装阻抗，减少无谓的状态转换代码。</p>
<h3>IOContext 是什么？</h3>
<p dir="auto">对于初接触异步网络库的读者，可以简单将 <code>IOContext</code> 理解为<strong>事件收割机与协程调度中枢</strong>。</p>
<p dir="auto">在代码中，你提交的各种异步操作（即 <code>io_uring</code> 的 SQE 事件），最终都需要一个统一的执行流去收割它们的完成结果（CQE）。成熟的模式是借鉴 Boost.Asio 的 <code>io_context</code> 抽象：通过阻塞调用 <code>IOContext::run()</code> 来消耗掉所有已就绪事件，唤醒对应的协程，然后继续等待下一轮事件就绪。</p>
<h3>构建基础框架</h3>
<p dir="auto">基于 C++ 的 RAII 原则，<code>IOContext</code> 的首要任务是管理 <code>io_uring</code> 实例的生命周期。</p>
<h4>1. 实例初始化</h4>
<pre><code class="language-cpp">int io_uring_queue_init(unsigned entries, struct io_uring* ring, unsigned flags);
</code></pre>
<ul>
<li><code>entries</code>: 提交队列（SQ）的深度。必须是 2 的幂（如 128, 256）。内核会基于此分配共享内存环。</li>
<li><code>ring</code>: 指向待初始化的实例。成功后，内存映射地址、队列掩码等状态将被写入该结构。</li>
<li><code>flags</code>: 控制行为的标志位（如启用 <code>SQPOLL</code> 消除系统调用）。我们这里默认置 0 即可。</li>
</ul>
<p dir="auto">失败时直接返回负值的系统错误码，不依赖全局 <code>errno</code>。</p>
<h4>2. 实例销毁</h4>
<pre><code class="language-cpp">void io_uring_queue_exit(struct io_uring* ring);
</code></pre>
<p dir="auto">该函数负责解除内存映射 (<code>munmap</code>)，并关闭 <code>io_uring</code> 在内核中对应的匿名文件描述符，防止虚拟内存与文件句柄泄漏。</p>
<h4>IOContext 资源管理骨架</h4>
<p dir="auto">基于上述 API，我们搭建出 <code>IOContext</code> 的核心轮廓。由于该上下文作为核心中枢运转，移动语义会引发悬垂指针等复杂问题，因此我们在设计上严格禁用拷贝与移动。</p>
<pre><code class="language-cpp">#include &lt;liburing.h&gt;
#include &lt;atomic&gt;

class IOContext {
public:
    explicit IOContext(unsigned entries)
    {
        if (::io_uring_queue_init(entries, &amp;ring_, 0) &lt; 0) {
            xin::throw_system_error("io_uring_queue_init");            
        }
    }

    IOContext(const IOContext&amp;) = delete;
    auto operator=(const IOContext&amp;) -&gt; IOContext&amp; = delete;

    // 为了简化实现，我们不支持移动
    IOContext(IOContext&amp;&amp;) = delete;
    auto operator=(IOContext&amp;&amp;) -&gt; IOContext&amp; = delete;

    ~IOContext()
    {
        ::io_uring_queue_exit(&amp;ring_);
    }

    &lsqb;&lsqb;nodiscard&rsqb;&rsqb; auto ring() noexcept -&gt; ::io_uring* { return &amp;ring_; }
    &lsqb;&lsqb;nodiscard&rsqb;&rsqb; auto ring() const noexcept -&gt; const ::io_uring* { return &amp;ring_; }

private:
    ::io_uring ring_;
};
</code></pre>
<h3>收割已就绪事件 (CQE)</h3>
<p dir="auto">接下来实现核心引擎 <code>IOContext::run()</code>。这涉及三个底层操作流：</p>
<ol>
<li>
<p dir="auto"><strong>等待事件就绪</strong>：<br />
<code>io_uring_submit_and_wait(struct io_uring* ring, unsigned wait_nr);</code></p>
<p dir="auto">将提交操作和阻塞等待融合成一次系统调用。<code>wait_nr</code> 指明线程必须阻塞到至少出现多少个完成事件才唤醒返回。</p>
<p dir="auto"><strong>返回值陷阱</strong>：成功时返回的是提交的 SQE 数量，而非完成的 CQE 数量。</p>
</li>
<li>
<p dir="auto"><strong>遍历完成队列 (CQ)</strong>：<br />
<code>io_uring_for_each_cqe</code> 是一个纯用户态宏。它通过带有 <em>Acquire</em> 语义的内存屏障读取 CQ 尾指针，无锁且零拷贝地遍历就绪事件。</p>
<p dir="auto"><strong>状态剥离陷阱</strong>：该宏只是只读遍历，不修改内核视角的头部指针。如果仅仅遍历而不推进状态，队列最终会溢出导致 <code>-EBUSY</code>。</p>
</li>
<li>
<p dir="auto"><strong>确认事件消费</strong>：<br />
<code>io_uring_cq_advance(struct io_uring* ring, unsigned nr);</code></p>
<p dir="auto">修改用户空间的 Head 指针，并通过 <em>Store-Release</em> 语义发布给内核，正式确认这些事件已被收割。</p>
</li>
</ol>
<h4>user_data 与类型擦除</h4>
<p dir="auto"><code>io_uring_cqe</code> 结构中包含一个 <code>__u64 user_data</code> 字段。当我们在 SQE 中设置它时，内核会原封不动地将其带入 CQE 返回。这使得我们能够将该标识强制转换回 C++ 对象的指针。</p>
<p dir="auto">为此，我们提供一个 <code>Operation</code> 基类，所有协程 Awaiter 都必须继承此接口：</p>
<pre><code class="language-cpp">struct Operation {
    virtual ~Operation() = default;
    virtual void complete(int res, unsigned flags) = 0;
};
</code></pre>
<h4>优雅的退出：should_stop_ 的无锁设计</h4>
<p dir="auto">为了安全退出事件循环，我们引入 <code>should_stop_</code> 变量。即便网络库采用 Core Per Thread 模型，不涉及跨业务线程的同步，但 <code>stop()</code> 操作往往是由操作系统的信号处理器（Signal Handler，如处理 Ctrl+C）触发的。信号中断具有强抢占性，因此必须使用 <code>std::atomic</code>。</p>
<p dir="auto">值得注意的是，这里我们<strong>不使用 CAS（Compare-And-Swap）</strong>。由于停止是一个幂等且无条件的覆盖动作，我们完全不关心过去的运行状态。直接使用 <code>store</code> 配合最松散的 <code>std::memory_order_relaxed</code> 即可。这提供了硬件级别的防数据撕裂保证，同时将同步开销降到了绝对的最低点。</p>
<blockquote>
<p dir="auto">WARN: 只有这个变量显然是不足以完整实现 stop 功能的，还需要考虑如何取消已经提交但尚未完成的 I/O 请求，以及如何通知正在等待的 run() 方法尽快返回。我们将在未来的版本中逐步完善这个功能。</p>
</blockquote>
<h4>完整的 run() 实现</h4>
<p dir="auto">结合外部任务追踪机制，事件循环的最终代码如下：</p>
<pre><code class="language-cpp">class IOContext {
    // ... 构造与析构保持不变 ...

    void run()
    {
        ::io_uring_cqe* cqe{ nullptr };

        while (!should_stop_.load(std::memory_order_relaxed) &amp;&amp; outstanding_works_ &gt; 0)
        {
            auto res = ::io_uring_submit_and_wait(&amp;ring_, 1);
            if (res &lt; 0)
                xin::throw_system_error("io_uring_submit_and_wait");

            unsigned head;
            unsigned count{ 0 };

            io_uring_for_each_cqe(&amp;ring_, head, cqe) {
                ++count;

                if (cqe-&gt;user_data != 0) {
                    auto* op = reinterpret_cast&lt;Operation*&gt;(cqe-&gt;user_data);
                    op-&gt;complete(cqe-&gt;res, cqe-&gt;flags);
                }
            }

            if (count &gt; 0) {
                outstanding_works_ -= count;
                ::io_uring_cq_advance(&amp;ring_, count);
            }
        }
    }

    &lsqb;&lsqb;nodiscard&rsqb;&rsqb; 
    auto sqe() -&gt; ::io_uring_sqe*
    {
        auto* sqe = ::io_uring_get_sqe(&amp;ring_);
        if (!sqe)
            xin::throw_system_error("io_uring_get_sqe");
        
        add_work();
        return sqe;
    }

    void stop() noexcept { should_stop_.store(true, std::memory_order_relaxed); }

    // 为了搭配co_spawn，需要暴露add_work和drop_work方法
    void add_work() noexcept { ++outstanding_works_; }

    void drop_work() noexcept
    {
        assert(outstanding_works_ &gt; 0);
        --outstanding_works_;
    }

private:
    ::io_uring ring_;
    std::size_t outstanding_works_{ 0 };
    std::atomic&lt;bool&gt; should_stop_{ false };

};
</code></pre>
<h3>深入 Awaiter 机制：SleepAwaiter 实践</h3>
<p dir="auto">单有一个 <code>IOContext</code> 是跑不起来的，我们需要验证它与 C++20 协程的交互机制。在此，我们实现一个 <code>SleepAwaiter</code>，封装 <code>io_uring</code> 的 <code>IORING_OP_TIMEOUT</code> 定时器。</p>
<pre><code class="language-cpp">#include &lt;chrono&gt;
#include &lt;coroutine&gt;
#include &lt;expected&gt;
#include &lt;system_error&gt;
#include &lt;utility&gt;

class SleepAwaiter : public Operation {
public:
    template&lt;typename Duration&gt;
    SleepAwaiter(IOContext&amp; context, Duration d) noexcept
      : context_{ context }
    {
        using namespace std::chrono;
        ts_.tv_sec = duration_cast&lt;seconds&gt;(d).count();
        ts_.tv_nsec = duration_cast&lt;nanoseconds&gt;(d % seconds(1)).count();
    }

    &lsqb;&lsqb;nodiscard&rsqb;&rsqb; constexpr auto await_ready() const noexcept -&gt; bool { return false; }

    void await_suspend(std::coroutine_handle&lt;&gt; handle) noexcept
    {
        handle_ = handle;
        auto* sqe = context_.sqe();

        // 提交纯超时指令，count 设为 0 表示只受时间触发
        ::io_uring_prep_timeout(sqe, &amp;ts_, 0, 0);
        ::io_uring_sqe_set_data(sqe, this);
    }

    &lsqb;&lsqb;nodiscard&rsqb;&rsqb; auto await_resume() const noexcept -&gt; std::expected&lt;void, std::error_code&gt;
    {
        // io_uring 中，超时正常结束会返回 -ETIME
        if (error_code_ == -ETIME || error_code_ == 0) {
            return {};
        }
        // 其他错误（如 -ECANCELED 被提前强杀）
        return std::unexpected{ std::error_code{ -error_code_, std::generic_category() } };
    }

    void complete(int res, &lsqb;&lsqb;maybe_unused&rsqb;&rsqb; std::uint32_t flags) noexcept override
    {
        error_code_ = res;
        if (handle_) {
            auto handle = std::exchange(handle_, nullptr);
            handle.resume();
        }
    }

private:
    IOContext&amp; context_;
    struct __kernel_timespec ts_{};
    std::coroutine_handle&lt;&gt; handle_{ nullptr };
    int error_code_{ 0 };
};

template&lt;typename Duration&gt;
&lsqb;&lsqb;nodiscard&rsqb;&rsqb; auto sleep_for(IOContext&amp; context, Duration duration) noexcept -&gt; SleepAwaiter
{
    return SleepAwaiter{ context, duration };
}
</code></pre>
<h4>零开销生命周期管理</h4>
<p dir="auto">留意 <code>await_suspend</code> 中的 <code>::io_uring_prep_timeout(sqe, &amp;ts_, 0, 0);</code>。我们将局部对象 <code>ts_</code> 的地址交给了内核。在传统的异步回调编程中，这是一个极易触发悬垂指针的致命错误，通常需要用 <code>std::shared_ptr</code> 在堆上分配来强行续命。</p>
<p dir="auto">但在这里，它是<strong>绝对安全的</strong>。因为 <code>SleepAwaiter</code> 本身的生命周期被牢牢绑定在了协程帧内部。直到 <code>complete</code> 回调中触发 <code>handle.resume()</code> 彻底唤醒协程后，该 Awaiter 才会被销毁。协程从语言底层提供了天然的内存安全保障，这也是 C++ 追求零开销抽象的绝佳体现。</p>
<h4>测试示例</h4>
<p dir="auto">最后，我们用一段简单的代码来验证整个基建流转：</p>
<pre><code class="language-cpp">auto demo(IOContext&amp; context) -&gt; xin::async::Task&lt;void&gt;
{
    using namespace std::chrono_literals;
    xin::log::info("before sleep...");    
    
    co_await sleep_for(context, 5s);

    xin::log::info("after sleep...");
}

int main(int argc, char* argv[])
{
    IOContext context{128};
    // 搭配协程任务分离器运行
    xin::async::co_spawn(context, demo(context));

    context.run();
    return EXIT_SUCCESS;
}
</code></pre>
<p dir="auto">输出如下，可以看到，5s后再次输出内容，这证明从请求提交、内核响应、上下文分发到协程唤醒的全链路已完全贯通：</p>
<pre><code class="language-bash">blog.io_context_v1
[2026-04-19 16:18:17.642] [info] before sleep...
[2026-04-19 16:18:22.642] [info] after sleep...
</code></pre>
<p dir="auto"><a href="https://github.com/Doomjustin/xin/blob/main/blog/io_context_v1.cpp" rel="nofollow ugc">完整代码详见</a></p>
]]></description><link>http://forum.d2learn.org/topic/189/基于-io_uring-的-c-20-协程网络库-01-基础骨架与-awaiter-机制</link><guid isPermaLink="true">http://forum.d2learn.org/topic/189/基于-io_uring-的-c-20-协程网络库-01-基础骨架与-awaiter-机制</guid><dc:creator><![CDATA[Doomjustin]]></dc:creator><pubDate>Invalid Date</pubDate></item><item><title><![CDATA[启动并分离 - co_spawn]]></title><description><![CDATA[<p dir="auto">在基于 C++20 协程构建的异步框架里，<code>Task&lt;T&gt;</code> 通常有着严格的结构化并发语义：调用者必须去 <code>co_await</code> 它，子任务的生命周期被死死地绑在父任务上。</p>
<p dir="auto">但现实世界没这么理想，系统架构中必然存在<strong>同步世界与异步世界的交汇点</strong>。最典型的例子就是 TCP 服务器的事件循环：</p>
<pre><code class="language-cpp">// 这是一个底层的同步事件循环
void server_accept_loop(io_context&amp; ctx) {
    while (true) {
        stream_socket client = accept_connection(ctx);
        
        // 业务协程：处理单个客户端连接
        // 函数签名：Task&lt;void&gt; handle_client(stream_socket client);
        
        // 问题：怎么在这里启动 handle_client，然后不管它，直接去接下一个客？
        
        // 方案 1: 直接调用？没用，返回值 Task 被丢弃，协程根本不会跑（惰性求值）。
        // handle_client(std::move(client)); 
        
        // 方案 2: 使用 co_await？编译直接报错！因为当前函数是个普通函数，不是协程。
        // co_await handle_client(std::move(client));

        // 方案 3: 把server_accept_loop的返回值改成Task&lt;void&gt;。这里使用co_await
        // 可以编译，但是一次只能处理一个client
    }
}
</code></pre>
<p dir="auto">为了解决这个问题，我们必须提供一个“启动并分离（Fire and Forget）”的方法，类似std::thread的detach模式。这没法用常规的 <code>Task&lt;T&gt;</code> 表达，我们需要自己捏一个底层原语：<code>co_spawn</code>。</p>
<p dir="auto">下面我们将从零开始，一步步打磨出一个内存安全、支持优雅停机的 <code>co_spawn</code>。</p>
<hr />
<h3>1. 裸分离：让编译器帮我们擦屁股</h3>
<p dir="auto">要把一个任务扔到后台，第一个要面对的灵魂拷问就是：<strong>这个协程在堆上分配的内存帧（Coroutine Frame），最后谁来删？</strong></p>
<p dir="auto">既然分离出去了，外部就不存在任何变量持有它的句柄。在 C++20 里，最优雅的解法是：<strong>配置好参数，让编译器自己管理。</strong></p>
<p dir="auto">按照协程规范，只要 <code>promise_type::final_suspend()</code> 返回 <code>std::suspend_never</code>，协程走到生命周期尽头时，运行时就会自动 <code>delete</code> 掉那块堆内存。据此，我们可以写出一个极简的“裸分离”壳子：</p>
<pre><code class="language-cpp">// 纯粹的空壳，仅用于触发编译器的自动清理
struct DetachedTask {
    struct promise_type {
        auto get_return_object() noexcept { return detached{}; }
        
        // 饥饿启动：一创建就立马执行
        auto initial_suspend() noexcept { return std::suspend_never{}; }
        
        // 核心：结束时不挂起，触发自动销毁
        auto final_suspend() noexcept { return std::suspend_never{}; }
        
        void return_void() noexcept {}
        void unhandled_exception() noexcept { std::terminate(); }
    };
};

// 极简版原语
template&lt;typename Awaitable&gt;
auto co_spawn(Awaitable awaitable) -&gt; DetachedTask 
{
    co_await std::move(awaitable);
}
</code></pre>
<p dir="auto">然后，我们在同步回调里调用 <code>co_spawn(handle_client(std::move(client)));</code> 时，协程帧会立即投入运行。遇到 I/O 挂起时，控制流会 <code>return</code> 回主循环（不会阻塞线程！）。等任务彻底跑完，走向 <code>final_suspend</code>，内存安全摧毁，干干净净。</p>
<h3>2. 状态追踪：用 RAII 告别“幽灵任务”</h3>
<p dir="auto">上面这套“裸分离”虽然在语言机制上跑得通，但在工程上其实是个定时炸弹。因为它<strong>没法做状态追踪，也就没法支持服务器的优雅停机（Graceful Shutdown）</strong>。</p>
<p dir="auto">试想一下，如果你发个 <code>SIGTERM</code> 准备关进程，底层的事件分发器怎么知道还有多少个 <code>co_spawn</code> 出去的任务在挂起等 I/O？如果直接把底层上下文销毁了，等这些“幽灵任务”被唤醒时，面对的就是一片废墟，当场 Core Dump。</p>
<p dir="auto">所以，分离出的协程必须和底层的上下文绑定生命周期：诞生时登记，死亡时注销。<br />
这里我们假定一个 <code>context</code> 应该支持 <code>add_work()</code> 和 <code>drop_work()</code> 来管理分离出去的任务。</p>
<pre><code class="language-cpp">// 假定上下文支持增减引用计数
template&lt;typename T&gt;
concept tracking_context = requires(T&amp; ctx) 
{
    ctx.add_work();
    ctx.drop_work();
};

template&lt;tracking_context Context&gt;
struct DetachedTask {
    struct promise_type {
        Context* context = nullptr;

        // 拦截参数：拿到上下文引用，生命周期开始时登记
        template&lt;typename Awaitable&gt;
        promise_type(Context&amp; ctx, Awaitable&amp;&amp;) 
          : context{ &amp;ctx } 
        {
            context-&gt;add_work(); 
        }

        // 绑定析构：随协程帧被编译器销毁时，自动注销
        ~promise_type() 
        {
            if (context) context-&gt;drop_work(); 
        }

        auto get_return_object() noexcept { return DetachedTask{}; }
        auto initial_suspend() noexcept { return std::suspend_never{}; }
        auto final_suspend() noexcept { return std::suspend_never{}; }
        void return_void() noexcept {}
        void unhandled_exception() noexcept { std::terminate(); }
    };
};
</code></pre>
<h3>3. 最终的 <code>co_spawn</code></h3>
<p dir="auto">有了上面这个支持状态追踪的 <code>detached_task</code>，我们就可以给出 <code>co_spawn</code> 的最终接口了。</p>
<pre><code class="language-cpp">// 注意这里的 Awaitable awaitable 是按值传递！
template&lt;tracking_context Context, awaitable Awaitable&gt;
    requires std::movable&lt;std::remove_cvref_t&lt;Awaitable&gt;&gt;
auto co_spawn(Context&amp; ctx, Awaitable awaitable) -&gt; DetachedTask&lt;Context&gt; 
{
    // 移动进协程帧里，生命周期交给DetachedTask
    co_await std::move(awaitable);
}
</code></pre>
<p dir="auto"><strong>为什么要Awaitble必须按值传？</strong><br />
在协程里，如果参数是引用，堆上的协程帧就只会存个指针。像 <code>handle_client(std::move(client))</code> 这种调用，产生的是个临时对象（右值）。如果 <code>co_spawn</code> 接的是个引用，等它内部第一次 <code>co_await</code> 挂起、把控制权还给外层时，这个临时对象早就析构了！这会导致极其隐蔽的悬垂引用Bug。</p>
<p dir="auto">通过强制按值传递，我们用移动语义，把临时的业务任务移动到了协程帧内部，只要协程不死，它的状态就绝对安全。</p>
<h3>4. 补充concept：到底什么是 <code>awaitable</code>？</h3>
<p dir="auto">细心的朋友肯定注意到了，在最终的 <code>co_spawn</code> 签名里，我用了一个 <code>awaitable</code> 的概念。</p>
<p dir="auto">在 C++20 里，一个东西能被 <code>await</code>，无非三种情况：</p>
<ol>
<li>它自己就是个 <code>awaiter</code>，即有3个await函数</li>
<li>它重载了成员方法 <code>operator co_await()</code></li>
<li>有对应的全局重载。</li>
</ol>
<p dir="auto">我们就把这个concept用代码翻译出来：</p>
<pre><code class="language-cpp">template&lt;typename T&gt;
concept awaiter = requires(T&amp; t, std::coroutine_handle&lt;&gt; handle)
{
    { t.await_ready() } -&gt; std::convertible_to&lt;bool&gt;;
    t.await_suspend(handle);
    t.await_resume();
};

template&lt;typename T&gt;
concept has_operator_co_await = requires(T&amp;&amp; t)
{
    { std::forward&lt;T&gt;(t).operator co_await() } -&gt; awaiter;
};

template&lt;typename T&gt;
concept has_global_operator_co_await = requires(T&amp;&amp; t)
{
    { operator co_await(std::forward&lt;T&gt;(t)) } -&gt; awaiter;
};

template&lt;typename T&gt;
concept awaitable = awaiter&lt;T&gt; 
                 || has_operator_co_await&lt;T&gt; 
                 || has_global_operator_co_await&lt;T&gt;;
</code></pre>
<hr />
<h3>实战演示</h3>
<p dir="auto">最后，给一段伪代码示例，看看它是怎么在业务里落地的：</p>
<pre><code class="language-cpp">import std;

// 1. 实现一个满足 tracking_context 契约的上下文
struct MyIOContext {
    int active_tasks = 0;
    
    void add_work() 
    {
        ++active_tasks;
        std::cout &lt;&lt; "[Context] 任务+1，当前活跃数: " &lt;&lt; active_tasks &lt;&lt; "\n";
    }
    
    void drop_work() 
    {
        --active_tasks;
        std::cout &lt;&lt; "[Context] 任务结束，当前活跃数: " &lt;&lt; active_tasks &lt;&lt; "\n";
    }
    
    void run_loop() 
    {
        // 真实场景里，这里是 epoll_wait 或 io_uring_enter 阻塞等事件
        std::cout &lt;&lt; "[Context] 开启事件循环，等待 I/O...\n";
    }
};

// 2. 模拟一个能被 co_await 的异步操作 (满足 awaitable 契约)
struct DummyAsyncRead {
    bool await_ready() { return false; }
    void await_suspend(std::coroutine_handle&lt;&gt;) 
    {
        std::cout &lt;&lt; "  -&gt; 协程挂起，把 fd 注册到 epoll...\n";
    }

    void await_resume() 
    {
        std::cout &lt;&lt; "  -&gt; 协程恢复，拿到数据！\n";
    }
};

// 业务逻辑协程
DummyAsyncRead handle_client(int client_fd) 
{
    std::cout &lt;&lt; "开始处理客户端: " &lt;&lt; client_fd &lt;&lt; "\n";
    // 遇到 IO 挂起
    co_await DummyAsyncRead{}; 
}

// 3. 跑起来
int main() {
    MyIOContext ctx;
    
    std::cout &lt;&lt; "--- 服务器启动 ---\n";
    
    // 启动并分离，立刻返回
    co_spawn(ctx, handle_client(1001));
    co_spawn(ctx, handle_client(1002));
    
    std::cout &lt;&lt; "--- 同步的 main 函数丝毫不受阻塞 ---\n";
    
    ctx.run_loop();
    
    return 0;
}
</code></pre>
<p dir="auto">一个更具体的demo可以看这里：<a href="https://github.com/Doomjustin/xin/blob/main/demo/demo.cpp" rel="nofollow ugc">demo</a></p>
<h3>完整代码</h3>
<pre><code class="language-c++">
template&lt;typename T&gt;
concept awaiter = requires(T&amp; t, std::coroutine_handle&lt;&gt; handle)
{
    { t.await_ready() } -&gt; std::convertible_to&lt;bool&gt;;
    t.await_suspend(handle);
    t.await_resume();
};

template&lt;typename T&gt;
concept has_operator_co_await = requires(T&amp;&amp; t)
{
    { std::forward&lt;T&gt;(t).operator co_await() } -&gt; awaiter;
};

template&lt;typename T&gt;
concept has_global_operator_co_await = requires(T&amp;&amp; t)
{
    { operator co_await(std::forward&lt;T&gt;(t)) } -&gt; awaiter;
};

template&lt;typename T&gt;
concept awaitable = awaiter&lt;T&gt; 
                 || has_operator_co_await&lt;T&gt; 
                 || has_global_operator_co_await&lt;T&gt;;

template&lt;typename T&gt;
concept tracking_context = requires(T&amp; ctx)
{
    ctx.add_work();
    ctx.drop_work();
};

template&lt;tracking_context Context&gt;
struct DetachedTask {
    struct promise_type {
        Context* context = nullptr;

        template&lt;typename Awaitable&gt;
        promise_type(Context&amp; ctx, Awaitable&amp;&amp; awaitable)
          : context{ &amp;ctx }
        {
            context-&gt;add_work();
        }

        ~promise_type()
        {
            context-&gt;drop_work();
        }

        auto get_return_object() noexcept { return DetachedTask{}; }

        auto initial_suspend() noexcept { return std::suspend_never{}; }

        auto final_suspend() noexcept
        {
            return std::suspend_never{};
        }

        void return_void() noexcept {}

        void unhandled_exception() noexcept
        {
            std::terminate();
        }
    };
};

export template&lt;tracking_context Context, awaitable Awaitable&gt;
    requires std::movable&lt;std::remove_cvref_t&lt;Awaitable&gt;&gt;
auto co_spawn(Context&amp; ctx, Awaitable awaitable) -&gt; DetachedTask&lt;Context&gt;
{
    co_await std::move(awaitable);
}
</code></pre>
]]></description><link>http://forum.d2learn.org/topic/188/启动并分离-co_spawn</link><guid isPermaLink="true">http://forum.d2learn.org/topic/188/启动并分离-co_spawn</guid><dc:creator><![CDATA[Doomjustin]]></dc:creator><pubDate>Invalid Date</pubDate></item><item><title><![CDATA[从零构建基于 C++20 的 Task]]></title><description><![CDATA[<p dir="auto">C++20 引入了无栈协程（Stackless Coroutines）的核心语言机制，但与之相配套的标准库高级抽象（如 <code>std::task</code>）并未同步提供。在构建基于 <code>io_uring</code> 或 <code>epoll</code> 的高性能并发框架时，我们不可避免地需要自行设计一个用于封装异步操作的返回类型：<code>Task&lt;T&gt;</code>。</p>
<p dir="auto">设计这样一个任务类型，不仅仅是对新关键字的语法包装，其本质是在解决两个系统级编程的核心问题：</p>
<ol>
<li><strong>控制流的无缝路由</strong></li>
<li><strong>堆分配状态帧的确定性释放</strong>。</li>
</ol>
<p dir="auto">本文将探讨如何从零构建一个可用的Task&lt;T&gt;。</p>
<h2>1. 异步组合的困境与懒启动（Lazy Evaluation）</h2>
<p dir="auto">在传统的同步流中，函数的调用即意味着执行的开始。但在异步架构中，任务的“构造”与“执行”往往需要被严格分离。</p>
<p dir="auto">为了建立直观的理解，我们可以先参考 Python 中的协程行为。在 Python 中，调用一个 <code>async def</code> 函数并不会立即执行其内部代码，而是仅仅返回一个协程对象：</p>
<pre><code class="language-python">import asyncio

async def fetch_data():
    print("开始发起网络请求...")
    # ...

# 此时并不会打印任何内容，仅仅是构造了一个任务对象
task = fetch_data() 

# 只有显式地等待或交给事件循环，代码才会真正运转
# await task 
</code></pre>
<p dir="auto">这种机制被称为<strong>懒启动（Lazy Evaluation）</strong>。如果我们允许 C++ 的协程在被调用时立即开始执行（即所谓的 Eager Evaluation），它可能会在尚未正确挂载到事件循环（Event Loop）之前，就过早地触发了底层的 I/O 投递操作。这不仅破坏了状态的封装，还极易引发复杂的竞态条件。</p>
<p dir="auto">因此，一个健壮的 C++ <code>Task&lt;T&gt;</code> 必须是懒启动的。这在 C++20 中是通过定制 <code>promise_type</code> 的初始化行为来实现的：</p>
<pre><code class="language-cpp">class promise_type {
public:
    // 协程帧创建后立即挂起，不主动执行协程体代码
    auto initial_suspend() noexcept -&gt; std::suspend_always { return {}; }
    // ...
};
</code></pre>
<p dir="auto">通过返回 <code>std::suspend_always</code>，协程在完成内部状态帧的堆分配后会立刻交出控制权。这种设计使得异步任务可以像普通的数据结构一样被安全地传递、存储和组合，直到调用者显式地通过 <code>co_await</code> 来驱动它。</p>
<h2>2. 协程间的控制流移交</h2>
<p dir="auto">异步操作很少是孤立存在的。当父协程执行 <code>co_await child_task;</code> 时，当前的执行流必须被挂起，并将 CPU 的控制权移交给子协程。同时，子协程必须知晓在自身执行完毕后，应该唤醒哪一个调用者。</p>
<p dir="auto">为了建立这种调用链，我们利用了 <code>co_await</code> 运算符所触发的编译器协议。</p>
<p dir="auto">在 C++20 中，<code>co_await</code> 并非一个简单的挂起指令，而是一个<strong>可定制的控制流拦截点</strong>。当编译器遇到 <code>co_await &lt;expr&gt;</code> 时，它会要求 <code>&lt;expr&gt;</code> 产出一个符合特定接口的 <code>Awaiter</code> 对象，并依次调用其三个核心方法：</p>
<ol>
<li><strong><code>await_ready()</code></strong>：探测状态。询问异步操作是否已经完成。如果返回 <code>true</code>，编译器将走“快速通道”，直接跳过挂起阶段；如果返回 <code>false</code>，则准备挂起当前协程。</li>
<li><strong><code>await_suspend(std::coroutine_handle&lt;&gt;)</code></strong>：核心拦截点。在当前协程的物理状态（寄存器、局部变量）被安全保存到堆上的协程帧后，编译器会调用此方法，并将当前（父）协程的句柄作为参数传入。</li>
<li><strong><code>await_resume()</code></strong>：结果提取点。当协程被再次唤醒时，此方法的返回值将作为整个 <code>co_await</code> 表达式的结果。</li>
</ol>
<p dir="auto">基于这一协议，我们在 <code>Task</code> 内部定义了专门的 <code>Awaiter</code>，以此来接管并路由控制流：</p>
<pre><code class="language-cpp">class Awaiter {
public:
    explicit Awaiter(handle_type handle) : handle_{ handle } {}

    // 1. 探测状态：如果子协程尚未执行完毕，则强制父协程挂起
    bool await_ready() const noexcept 
    { 
        return !handle_ || handle_.done(); 
    }

    // 2. 挂起时的控制流路由
    auto await_suspend(std::coroutine_handle&lt;&gt; next) -&gt; std::coroutine_handle&lt;&gt; 
    {
        // 将父协程的句柄 (next) 记录在子协程的 promise 状态中
        handle_.promise().next = next;
        // 返回子协程的句柄，指示 C++ 运行时将执行流切换至子协程
        return handle_;
    }

    // 3. 唤醒后的结果提取
    auto await_resume() const -&gt; T 
    {
        if (!handle_) throw std::logic_error{ "Invalid handle" };
        return handle_.promise().result();
    }

private:
    handle_type handle_; // 子协程的句柄
};
</code></pre>
<p dir="auto">通过这一套状态机转换，C++ 将底层的调度权完全下放给了库作者。</p>
<p dir="auto">在 <code>await_suspend</code> 执行的瞬间，父协程已被安全冻结。</p>
<p dir="auto">我们将其句柄保存在子协程的 <code>promise_type::next</code> 字段里，从而在内存中建立了一个单向的调用链表（父 -&gt; 子）。</p>
<p dir="auto">紧接着返回子协程的 <code>handle_</code>，运行时会直接跳转执行子协程代码，实现了零开销的上下文切换。</p>
<h2>3. 栈溢出风险与对称传输（Symmetric Transfer）</h2>
<p dir="auto">子协程执行到末尾（或遇到 <code>co_return</code>）时，需要唤醒之前等待它的父协程。这往往是自定义协程实现中最容易出错的环节。</p>
<p dir="auto">直觉上的做法是，在子协程的收尾阶段直接调用 <code>next.resume()</code>。然而，这种非对称传输（Asymmetric Transfer）存在致命缺陷：</p>
<p dir="auto"><code>resume()</code> 本质上是一个常规的同步函数调用。</p>
<p dir="auto">在网络服务这类存在深层嵌套或无限循环挂起的场景中（例如 <code>while(true) { co_await read(); }</code>），每一次 <code>resume()</code> 都会在操作系统的线程栈上压入一个新的栈帧。调用链越长，栈越深，最终必然导致 Stack Overflow（栈溢出）。</p>
<p dir="auto">为了提供工业级的稳定性，<code>Task</code> 在收尾时必须采用<strong>对称传输（Symmetric Transfer）</strong>：</p>
<pre><code class="language-cpp">class FinalAwaiter {
public:
    bool await_ready() const noexcept { return false; }

    template&lt;typename Promise&gt;
    auto await_suspend(std::coroutine_handle&lt;Promise&gt; handle) const noexcept -&gt; std::coroutine_handle&lt;&gt; 
    {
        auto next = handle.promise().next;
        // 关键点：直接返回父协程的句柄，而非调用 next.resume()
        return next ? next : std::noop_coroutine();
    }

    void await_resume() const noexcept {}
};

// 在 promise_type 中指定收尾行为：
auto final_suspend() noexcept -&gt; FinalAwaiter { return {}; }
</code></pre>
<p dir="auto">通过让 <code>final_suspend</code> 返回一个包含父协程句柄的 <code>Awaiter</code>，编译器会采用类似尾调用优化（Tail Call）的机制：</p>
<p dir="auto"><strong>它会首先将当前子协程的物理栈帧安全剥离，然后再以平级跳转的方式进入父协程。</strong></p>
<p dir="auto">在这种机制的保障下，无论业务逻辑中 <code>co_await</code> 嵌套了多少层，底层的线程调用栈深度始终保持恒定 (O(1))。</p>
<h2>4. 返回值的提取与异常路由</h2>
<p dir="auto">异步任务不仅涉及控制流的跳转，还必须安全地跨越挂起边界传递数据或异常，并且表现得如同普通的 C++ 函数调用一样。</p>
<p dir="auto">在子协程内部，产生的值或未捕获的异常被分别存储在 <code>promise_type</code> 的 <code>std::optional&lt;T&gt;</code> 和 <code>std::exception_ptr</code> 中。当父协程通过对称传输被唤醒，并执行 <code>await_resume()</code> 时，需要提取这些结果：</p>
<pre><code class="language-cpp">auto result() -&gt; T 
{
    if (exception_) 
        std::rethrow_exception(exception_);
    return std::move(value_).value();
}
</code></pre>
<p dir="auto">这里包含两个重要的设计约束：</p>
<ol>
<li><strong>异常透明性</strong>：<code>std::rethrow_exception</code> 确保了子协程中发生的异常能够被无缝抛出，并被父协程的 <code>try-catch</code> 块捕获，维持了 C++ 异常处理语义的连贯性。</li>
<li><strong>资源所有权转移</strong>：通过 <code>std::move</code> 提取值，保证了诸如 <code>std::unique_ptr</code> 或封装了系统资源（如文件描述符）的不可拷贝对象（Move-Only Types）能够被正确返回。</li>
</ol>
<h2>5. 协程帧的生命周期管理与单次消费语义</h2>
<p dir="auto">无栈协程的局部变量和 <code>promise_type</code> 被编译器分配在堆上的协程帧（Coroutine Frame）中。由于 C++ 没有垃圾回收机制，资源泄漏是协程编程中的主要风险之一。</p>
<p dir="auto">依据 C++ 核心的 RAII（资源获取即初始化）原则，<code>Task</code> 对象作为协程句柄的唯一持有者，理应负责这块内存的清理：</p>
<pre><code class="language-cpp">template&lt;typename T&gt;
class Task {
public:
    ~Task() 
    { 
        if (handle_) handle_.destroy(); 
    }

    // 限制为右值调用，且不转移 handle_ 的所有权
    auto operator co_await() &amp;&amp; noexcept { return Awaiter{ handle_ }; }
};
</code></pre>
<p dir="auto">这里有两个深思熟虑的设计权衡：</p>
<p dir="auto"><strong>第一：为什么限制 <code>operator co_await</code> 为右值版本（<code>&amp;&amp;</code>）？</strong><br />
协程代表一个异步计算过程，其内部结果（特别是前文提到的 Move-Only 类型）在 <code>await_resume</code> 中是被破坏性提取的（<code>std::move</code>）。这意味着一个 <code>Task</code> 在逻辑上只能被消费一次。如果允许对左值的 <code>Task</code> 进行 <code>co_await</code>，调用者可能会意外地多次等待同一个任务：</p>
<pre><code class="language-cpp">Task&lt;int&gt; t = do_work();
auto res1 = co_await t;
auto res2 = co_await t; // 错误：底层协程已经结束，状态帧已被销毁
</code></pre>
<p dir="auto">通过添加 <code>&amp;&amp;</code> 限定符，我们利用 C++ 的类型系统在编译期强制执行了“单次消费（Single-Shot）”语义。调用者必须直接等待临时对象（如 <code>co_await do_work();</code>），或者显式地转移所有权（<code>co_await std::move(t);</code>）。这在接口层面明确了状态机的生命周期契约。</p>
<p dir="auto"><strong>第二：为什么在右值版本中，依然不剥夺 Task 的所有权？</strong><br />
通常在处理右值时，我们会使用 <code>std::exchange</code> 来转移底层资源。但在这里，我们仅向 <code>Awaiter</code> 传递了句柄的值。<br />
当执行 <code>auto res = co_await do_work();</code> 时，<code>do_work()</code> 产生的 <code>Task</code> 临时对象的生命周期会被编译器自动延续，直到整个 <code>co_await</code> 表达式求值完毕（即 <code>await_resume()</code> 返回之后）。此时，临时 <code>Task</code> 对象被析构，从而触发 <code>handle_.destroy()</code>。<br />
如果我们在此处剥夺了 <code>Task</code> 的所有权，清理责任就会落空。这种保留所有权的设计，确保了无论是正常执行完毕还是因异常提前中断，底层堆内存都能依托 <code>Task</code> 临时对象的析构函数被可靠地回收，实现了严格的内存安全。</p>
<p dir="auto"><strong>补充说明</strong><br />
在 C++ 中，临时对象的生命周期会持续到包含它的完整表达式（Full-expression）结束（通常是遇到分号 ;）。</p>
<p dir="auto">当我们写下如下代码时：</p>
<pre><code class="language-c++">auto res = co_await do_something();
</code></pre>
<p dir="auto">编译器实际上会做如下展开（伪代码）：</p>
<pre><code class="language-c++">{
    // 1. 调用函数，产生临时的 Task 右值对象
    auto&amp;&amp; __tmp_task = do_something(); 
    
    // 2. 调用 operator co_await，产生临时的 Awaiter 对象
    auto&amp;&amp; __awaiter = __tmp_task.operator co_await();
    
    if (!__awaiter.await_ready()) {
        // 3. 挂起当前协程，并调用 await_suspend
        __awaiter.await_suspend(current_coro_handle);
        // &lt;--- 协程在这里彻底挂起，CPU 离开 ---&gt;
        // &lt;--- 时空流转，无论过了多久，终于被唤醒 ---&gt;
    }
    
    // 4. 唤醒后，调用 await_resume 提取结果
    auto res = __awaiter.await_resume();
    
} // 5. 完整表达式结束！按照构造的相反顺序销毁临时对象：先销毁 __awaiter，再销毁 __tmp_task
</code></pre>
<p dir="auto">关键点在于： 协程在挂起时，编译器非常清楚 __tmp_task 和 __awaiter 的生命周期需要跨越挂起点。因此，编译器不会把它们分配在容易被销毁的线程栈（Thread Stack）上，而是直接将它们作为局部变量，打包存储在“当前（父）协程的堆分配状态帧（Coroutine Frame）”中。</p>
<p dir="auto">这意味着：</p>
<p dir="auto">Task 对象在整个挂起期间一直安然无恙地活在堆内存里。</p>
<p dir="auto">唤醒时，Awaiter 也并没有在栈上重建，你访问的依然是挂起前保存在堆里的那个确切的 Awaiter 实例。</p>
<p dir="auto">Task 必定比 Awaiter 活得更久（先构造的后销毁）。</p>
<p dir="auto">因此，Awaiter 内部仅持有 handle_ 的一个浅拷贝是绝对安全的，Task 完全不需要把所有权 exchange 给 Awaiter。</p>
<h2>结语</h2>
<p dir="auto">设计一个现代 C++ 的 <code>Task</code> 类，并非对关键字的简单拼接，而是对执行流跳转和资源生命周期的精密编排。通过懒启动隔离控制流、利用对称传输突破调用栈限制、借助 RAII 保障内存释放，我们最终构建出了一个符合 C++ 哲学体系的高性能并发原语。</p>
<h3>完整代码</h3>
<pre><code class="language-c++">export module xin.task;

import std;

namespace xin {

class FinalAwaiter {
public:
    &lsqb;&lsqb;nodiscard&rsqb;&rsqb;
    constexpr auto await_ready() const noexcept -&gt; bool
    {
        return false;
    }

    template&lt;typename Promise&gt;
    auto await_suspend(std::coroutine_handle&lt;Promise&gt; handle) const noexcept -&gt; std::coroutine_handle&lt;&gt;
    {
        auto next = handle.promise().next;
        return next ? next : std::noop_coroutine();
    }

    void await_resume() const noexcept {}
};


export template&lt;typename T = void&gt;
class Task;

export template&lt;typename T&gt;
class Task {
public:
    class promise_type;
    using handle_type = std::coroutine_handle&lt;promise_type&gt;;

    class promise_type {
    public:
        auto get_return_object() noexcept -&gt; Task { return Task{ handle_type::from_promise(*this) }; }

        auto initial_suspend() noexcept -&gt; std::suspend_always { return {}; }

        auto final_suspend() noexcept -&gt; FinalAwaiter { return {}; }

        void unhandled_exception() noexcept { exception_ = std::current_exception(); }

        template&lt;typename U&gt;
            requires std::convertible_to&lt;U&amp;&amp;, T&gt;
        void return_value(U&amp;&amp; value) noexcept(std::is_nothrow_constructible_v&lt;T, U&amp;&amp;&gt;)
        {
            value_.emplace(std::forward&lt;U&gt;(value));
        }

        &lsqb;&lsqb;nodiscard&rsqb;&rsqb;
        auto result() -&gt; T
        {
            if (exception_)
                std::rethrow_exception(exception_);

            if (!value_)
                throw std::logic_error{ "No value returned from coroutine" };

            auto out = std::move(*value_);
            value_.reset();
            return out;
        }

        std::coroutine_handle&lt;&gt; next{ nullptr };

    private:
        std::exception_ptr exception_;
        std::optional&lt;T&gt; value_;
    };

    Task() = default;

    Task(handle_type handle)
      : handle_{ handle }
    {}

    Task(const Task&amp;) = delete;
    auto operator=(const Task&amp;) -&gt; Task&amp; = delete;

    Task(Task&amp;&amp; other) noexcept
      : handle_{ std::exchange(other.handle_, {}) }
    {}

    auto operator=(Task&amp;&amp; other) noexcept -&gt; Task&amp;
    {
        if (this == &amp;other)
            return *this;

        if (handle_)
            handle_.destroy();

        handle_ = std::exchange(other.handle_, nullptr);
        return *this;
    }

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

    &lsqb;&lsqb;nodiscard&rsqb;&rsqb;
    auto done() const noexcept -&gt; bool
    {
        return !handle_ || handle_.done();
    }

    &lsqb;&lsqb;nodiscard&rsqb;&rsqb;
    auto handle() const noexcept -&gt; handle_type
    {
        return handle_;
    }

    class Awaiter {
    public:
        explicit Awaiter(handle_type handle)
          : handle_{ handle }
        {}

        &lsqb;&lsqb;nodiscard&rsqb;&rsqb;
        auto await_ready() const noexcept -&gt; bool
        {
            return !handle_ || handle_.done();
        }

        auto await_suspend(std::coroutine_handle&lt;&gt; next) -&gt; std::coroutine_handle&lt;&gt;
        {
            handle_.promise().next = next;
            return handle_;
        }

        auto await_resume() const -&gt; T
        {
            if (!handle_)
                throw std::logic_error{ "Invalid coroutine handle" };

            return handle_.promise().result();
        }

    private:
        handle_type handle_;
    };

    auto operator co_await() &amp;&amp; noexcept { return Awaiter{ handle_ }; }

private:
    handle_type handle_{ nullptr };
};


export template&lt;&gt;
class Task&lt;void&gt; {
public:
    class promise_type;
    using handle_type = std::coroutine_handle&lt;promise_type&gt;;

    class promise_type {
    public:
        std::coroutine_handle&lt;&gt; next{ nullptr };

        auto get_return_object() noexcept -&gt; Task { return Task{ handle_type::from_promise(*this) }; }

        auto initial_suspend() noexcept -&gt; std::suspend_always { return {}; }

        auto final_suspend() noexcept -&gt; FinalAwaiter { return {}; }

        void unhandled_exception() noexcept { exception_ = std::current_exception(); }

        void return_void() noexcept {}

        void result()
        {
            if (exception_)
                std::rethrow_exception(exception_);
        }

    private:
        std::exception_ptr exception_;
    };

    Task() = default;

    Task(handle_type handle)
      : handle_{ handle }
    {}

    Task(const Task&amp;) = delete;
    auto operator=(const Task&amp;) -&gt; Task&amp; = delete;

    Task(Task&amp;&amp; other) noexcept
      : handle_{ std::exchange(other.handle_, nullptr) }
    {}

    auto operator=(Task&amp;&amp; other) noexcept -&gt; Task&amp;
    {
        if (this == &amp;other)
            return *this;

        if (handle_)
            handle_.destroy();

        handle_ = std::exchange(other.handle_, {});
        return *this;
    }

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

    &lsqb;&lsqb;nodiscard&rsqb;&rsqb;
    auto done() const noexcept -&gt; bool
    {
        return !handle_ || handle_.done();
    }

    &lsqb;&lsqb;nodiscard&rsqb;&rsqb;
    auto handle() const noexcept -&gt; handle_type
    {
        return handle_;
    }

    class Awaiter {
    public:
        explicit Awaiter(handle_type handle)
          : handle_{ handle }
        {
        }

        &lsqb;&lsqb;nodiscard&rsqb;&rsqb;
        auto await_ready() const noexcept -&gt; bool
        {
            return !handle_ || handle_.done();
        }

        auto await_suspend(std::coroutine_handle&lt;&gt; next) -&gt; std::coroutine_handle&lt;&gt;
        {
            handle_.promise().next = next;
            return handle_;
        }

        void await_resume() const
        {
            if (!handle_)
                throw std::logic_error{ "Invalid coroutine handle" };

            handle_.promise().result();
        }

    private:
        handle_type handle_;
    };

    auto operator co_await() &amp;&amp; noexcept { return Awaiter{ handle_ }; }

private:
    handle_type handle_{ nullptr };
};

} // namespace xin
</code></pre>
]]></description><link>http://forum.d2learn.org/topic/187/从零构建基于-c-20-的-task</link><guid isPermaLink="true">http://forum.d2learn.org/topic/187/从零构建基于-c-20-的-task</guid><dc:creator><![CDATA[Doomjustin]]></dc:creator><pubDate>Invalid Date</pubDate></item></channel></rss>