为了防止大家找不到代码,还是先贴一下项目链接:
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);
}
复制代码
总结
本文对如何获取协程的返回值做了介绍,分别介绍了不带超时的等待和带超时的等待,并且也介绍了如何处理无返回值函数的情况。
评论