迄今为止,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的协程和WorkflowSeries概念很相似,甚至可以互相切换(例如Coke中各种网络任务Server端的实现),但这也需要较深入的生命周期问题的说明,限于篇幅也暂不讨论了。

本系列文章同步发布于个人网站知乎专栏,转载请注明来源,以便读者及时获取最新内容及勘误。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注