写点什么

一个 cpp 协程库的前世今生(十一)等待与返回值

作者:SkyFire
  • 2022 年 1 月 05 日
  • 本文字数:2473 字

    阅读完需:约 8 分钟

一个cpp协程库的前世今生(十一)等待与返回值

为了防止大家找不到代码,还是先贴一下项目链接:


GitHub - skyfireitdiy/cocpp at cocpp-0.1.0


前面的文章介绍了协程跑起来的整个流程。本文介绍一下怎样获取协程执行结束之后的返回值。

返回值赋值

首先我们来回顾一下返回值是如何赋值的。


之前介绍过 ctx 中有一个成员 ret__用来存储返回值,这个成员会被传入到协程真正的入口函数中(source/cocpp/core/co_ctx.cpp):


void co_ctx::real_entry(co_ctx* ctx){    ctx->entry()(ctx->ret_ref());    // CO_DEBUG("ctx %s %p finished", ctx->config().name.c_str(), ctx);    ctx->set_state(co_state::finished);    ctx->finished().pub();    assert(ctx->env() != nullptr);    ctx->env()->schedule_switch(); // 此处的ctx对应的env不可能为空,如果为空,这个ctx就不可能被调度}
复制代码


然后在入口函数中对返回值进行赋值(include/cocpp/interface/co.h):


template <typename Func, typename... Args>void co::init__(co_ctx_config config, Func&& func, Args&&... args){    std::function<void(co_any&)> entry;    if constexpr (std::is_same_v<std::invoke_result_t<std::decay_t<Func>, std::decay_t<Args>...>, void>)    {        entry = [... args = std::forward<Args>(args), func = std::forward<Func>(func)](co_any& ret) mutable {            std::forward<Func>(func)(std::forward<Args>(args)...);        };    }    else    {        entry = [... args = std::forward<Args>(args), func = std::forward<Func>(func)](co_any& ret) mutable {            ret = std::forward<Func>(func)(std::forward<Args>(args)...);        };    }
ctx__ = manager__->create_and_schedule_ctx(config, entry, true);}
复制代码


所以当协程运行结束的时候,返回值就已经设置好了。

等待

我们通过 co::wait 接口可以获取到返回值(include/cocpp/interface/co.h)。


template <CoIsNotVoid Ret>Ret co::wait(){    return manager__->current_env()->wait_ctx(ctx__);}
复制代码


这里调用了当前 env 的 wait_ctx 成员函数。


实现如下(source/cocpp/core/co_env.cpp):


co_return_value co_env::wait_ctx(co_ctx* ctx){
auto old_priority = current_ctx()->priority(); current_ctx()->set_priority(ctx->priority());
CoDefer([this, old_priority] { current_ctx()->set_priority(old_priority); });
while (ctx->state() != co_state::finished) { schedule_switch(); } wait_ctx_finished().pub(ctx); return ctx->ret_ref();}
复制代码


此函数返回了 ctx 的 ret__成员的引用,但是注意一下他的返回类型。是 co_return_value ,现在来看一下这个类型(include/cocpp/core/co_return_value.h):


class co_return_value{    co_any value__; // 用来存储返回值public:    co_return_value(co_any value);                                      // 构造函数    co_return_value(const co_return_value& value) = default;            // 构造函数    co_return_value& operator=(const co_return_value& value) = default; // 赋值操作符    co_return_value& operator=(co_return_value&& value) = default;      // 赋值操作符
template <typename T> operator T(); // 类型转换操作符,如果类型不匹配会抛出异常};
template <typename T>co_return_value::operator T(){ return value__.get<T>();}
复制代码


实际上他只是将 co_any 显式提取真实值的接口替换为了隐式转换,这样可以隐藏底层细节。


另外我们注意到 co::wait 还有两个重载版本:


template <CoIsVoid Ret>Ret co::wait(){    manager__->current_env()->wait_ctx(ctx__);}
template <class Rep, class Period>std::optional<co_return_value> co::wait(const std::chrono::duration<Rep, Period>& wait_duration){ return manager__->current_env()->wait_ctx(ctx__, std::chrono::duration_cast<std::chrono::nanoseconds>(wait_duration));}
复制代码


第 1 个重载版本是针对没有返回值的入口函数,此处没有 return。


第 2 个重载版本是带超时的等待,调用了当前 env 对应的重载函数(source/cocpp/core/co_env.cpp):



std::optional<co_return_value> co_env::wait_ctx(co_ctx* ctx, const std::chrono::nanoseconds& timeout){ // 反转优先级,防止高优先级ctx永久等待低优先级ctx auto old_priority = current_ctx()->priority(); current_ctx()->set_priority(ctx->priority()); CoDefer([this, old_priority] { current_ctx()->set_priority(old_priority); });
std::optional<co_return_value> ret;
auto now = std::chrono::high_resolution_clock::now(); while (ctx->state() != co_state::finished) { if (std::chrono::high_resolution_clock::now() - now > timeout) { wait_ctx_timeout().pub(ctx); return ret; } schedule_switch(); } wait_ctx_finished().pub(ctx); return ctx->ret_ref();}
复制代码


这里需要注意的一点是,如果等待超时,协程还没有执行结束,这时需要返回什么呢?直接返回一个默认对象肯定是不对的,此时我们可以想到,标准库里的 std::optional 不就是为了应对此种情况么?通过判断 std::optional 的有效性来确定是由于超时返回还是协程结束返回。


因此其调用方式如下():


TEST(co, wait_timeout){    co   c1([]() {        this_co::sleep_for(std::chrono::seconds(1));    });    auto ret = c1.wait(std::chrono::milliseconds(1));    EXPECT_FALSE(ret);    ret = c1.wait(std::chrono::milliseconds(10000));    EXPECT_TRUE(ret);}
复制代码

总结

本文对如何获取协程的返回值做了介绍,分别介绍了不带超时的等待和带超时的等待,并且也介绍了如何处理无返回值函数的情况。

发布于: 刚刚
用户头像

SkyFire

关注

这个cpper很懒,什么都没留下 2018.10.13 加入

会一点点cpp的苦逼码农

评论

发布
暂无评论
一个cpp协程库的前世今生(十一)等待与返回值