迄今为止,Coke
通过七篇文章愉快地讨论了所有的基础组件,熟悉C++ Workflow的小伙伴可以很清楚地看出这些组件与Workflow
的基础任务一一对应,通过自由地组合这些基础组件,便可以实现各种绚丽多彩的复杂任务了。这时候就有骑电动车的读者要问了:“你这哪儿够十五斤哪?你这实现有问题呀”。
没错,与Workflow
相比,Coke
仅实现了一小部分功能,目前的文章中尚未涉及域名解析、服务治理、自定义协议、自定义任务等诸多功能。因此,在讨论更多的内容之前,我们需要先讨论一个问题:“Coke与Workflow其实是!一!回!事!”。Coke
只需要通过劫持task的回调函数来实现协程调度,其他与Workflow
相关的机制都可以灵活地使用。下面通过几个示例来说明这个问题。
coke::HttpClient
有些局限,我想使用Workflow
工厂创建的任务可以吗?可以!网络任务通常需要大量的参数来构建请求,在某些情况下使用Coke
提供的客户端可能不够方便,但这些客户端通常只是一个语法糖,用户仍可以自行创建任务并交给协程调度器发起任务,例如
coke::Task<> http() {
// 使用工厂创建任务,并设置相关参数
WFHttpTask *task = WFTaskFactory::create_http_task("http://sogou.com/", 1, 1, nullptr);
task->set_receive_timeout(1000);
task->get_req()->add_header_pair("User-Agent", "Coke example");
// task遵守与Workflow一致的生命周期,当http task交给HttpAwaiter后,一般不可再操作task
coke::HttpResult res = co_await coke::HttpAwaiter(task);
// 查看返回的结果
std::cout << res.state << ' ' << res.error << std::endl;
if (res.state == coke::STATE_SUCCESS)
std::cout << res.resp.get_status_code() << std::endl;
}
Workflow
可以通过派生来实现用户自己的任务,这种任务可以方便地接入Coke
吗?可以!Coke
无法预料到用户会创造出哪些奇思妙想的任务,因此提供了通用任务等待器来解决这个问题,只要这个任务继承自SubTask
且有回调机制,即可应用到Coke
当中。例如,我们可以在无需任何额外机制支持的情况下,在Coke
中使用基于Workflow
实现的优秀RPC框架SRPC。但限于篇幅,此处还是举个简单的例子
coke::Task<> generic() {
// 以ParallelWork为例,没错!ParallelWork是一种SubTask!
// coke并没有一种名为`ParallelAwaiter`的工具,但我们仍有办法异步等待
ParallelWork *p = Workflow::create_parallel_work(nullptr);
for (int i = 0; i < 10; i++) {
auto *t = WFTaskFactory::create_go_task("", [](){ std::cout << 1 << std::endl; });
auto *s = Workflow::create_series_work(t, nullptr);
p->add_series(s);
}
// 创建一个通用等待器,其模板参数为等待的返回值类型,如无需返回值则使用void即可
// 为了安全起见,`coke::GenericAwaiter`目前是不可复制和移动的类型
coke::GenericAwaiter<int> g;
// 没错,ParallelWork有回调机制
p->set_callback([&g](const ParallelWork *) {
// 引用捕获通用等待器,设置结果并调用`GenericAwaiter::done`
g.set_result(10);
g.done();
});
// 将任务p的所有权移交给GenericAwaiter管理,此后不可再对其进行操作
g.take_over(p);
// 异步等待任务完成,并获取结果
int ret = co_await g;
std::cout << ret << std::endl;
}
Workflow
可以直接启动一个Series
且无需等待结果,我可以在Coke
中这样做吗?可以!依作者拙见,C++ Workflow
应当是在没有编译器开洞的情况下,无栈协程的最优美形态了,原则上来说二者的表达能力应当等价。例如
coke::Task<> detach(long a, long b) {
co_await coke::sleep(a, b);
std::cout << "detach done" << std::endl;
}
void run() {
long sec = 0;
long usec = 1000;
// 1. 对于普通函数,直接start()即可
detach(sec, usec).start();
// 2. 对于函数对象等有状态的类型,若不能保证对象在函数结束后才被析构,则不可直接start
auto func = [sec, usec]() -> coke::Task<> { co_await coke::sleep(sec, usec); };
// func().start(); // 不合理
// 此时可以这样使用
coke::make_task(std::move(func)).start();
std::cout << "run done" << std::endl;
}
虽然随时detach一个协程看起来很方便,但最好通过计数等方式保证进程退出前,所有协程都已经正常结束。
对于不喜欢涉及生命周期问题的读者,可以只使用无状态的普通函数,其他状态都通过函数参数的形式传递即可。除此之外,Coke
的协程和Workflow
的Series
概念很相似,甚至可以互相切换(例如Coke
中各种网络任务Server端的实现),但这也需要较深入的生命周期问题的说明,限于篇幅也暂不讨论了。
本系列文章同步发布于个人网站和知乎专栏,转载请注明来源,以便读者及时获取最新内容及勘误。
本文由kedixa发表于个人博客,
转载请注明作者及出处。
本文链接:https://blog.kedixa.top/2023/coke-flexible-awaiter/