【问题标题】:How to set error_code to asio::yield_context如何将 error_code 设置为 asio::yield_context
【发布时间】:2018-04-01 19:25:04
【问题描述】:

我想创建一个异步函数,它的最后一个参数是 boost::asio::yield_context。例如:

int async_meaning_of_life(asio::yield_context yield);

我还想与 Asio 返回错误代码的方式保持一致。也就是说,如果用户这样做:

int result = async_meaning_of_life(yield);

函数失败,然后抛出system_error 异常。但如果用户这样做:

boost::error_code ec;
int result = async_meaning_of_life(yield[ec]);

然后 - 而不是抛出 - 在 ec 中返回错误。

问题是在实现该功能时,我似乎无法找到一种干净的方法来检查是否使用了 operator[] 并设置它。我们想出了这样的东西:

inline void set_error(asio::yield_context yield, sys::error_code ec)
{
    if (!yield.ec_) throw system_error(ec);
    *(yield.ec_) = ec;
}

但这很老套,因为yield_context::ec_declared private(尽管仅在文档中)。

我能想到的另一种方法是将yield 对象转换为asio::handler_type 并执行它。但这个解决方案充其量似乎很尴尬。

还有其他方法吗?

【问题讨论】:

    标签: c++ boost-asio asio boost-coroutine


    【解决方案1】:

    Asio 使用 async_result 在其 API 接口中透明地提供 use_futureyield_context 或完成处理程序。¹

    模式是这样的:

    template <typename Token>
    auto async_meaning_of_life(bool success, Token&& token)
    {
        typename asio::handler_type<Token, void(error_code, int)>::type
                     handler (std::forward<Token> (token));
    
        asio::async_result<decltype (handler)> result (handler);
    
        if (success)
            handler(42);
        else
            handler(asio::error::operation_aborted, 0);
    
        return result.get ();
    }
    

    更新

    从 Boost 1.66 开始,模式 adheres to the interface proposed 用于标准化:

        using result_type = typename asio::async_result<std::decay_t<Token>, void(error_code, int)>;
        typename result_type::completion_handler_type handler(std::forward<Token>(token));
    
        result_type result(handler);
    

    综合演示

    展示如何与 with 一起使用

    • coro 和产量[ec]
    • coro 和产量 + 异常
    • std::future
    • 完成处理程序

    Live On Coliru

    #define BOOST_COROUTINES_NO_DEPRECATION_WARNING 
    #include <iostream>
    #include <boost/asio.hpp>
    #include <boost/asio/spawn.hpp>
    #include <boost/asio/use_future.hpp>
    
    using boost::system::error_code;
    namespace asio = boost::asio;
    
    template <typename Token>
    auto async_meaning_of_life(bool success, Token&& token)
    {
    #if BOOST_VERSION >= 106600
        using result_type = typename asio::async_result<std::decay_t<Token>, void(error_code, int)>;
        typename result_type::completion_handler_type handler(std::forward<Token>(token));
    
        result_type result(handler);
    #else
        typename asio::handler_type<Token, void(error_code, int)>::type
                     handler(std::forward<Token>(token));
    
        asio::async_result<decltype (handler)> result (handler);
    #endif
    
        if (success)
            handler(error_code{}, 42);
        else
            handler(asio::error::operation_aborted, 0);
    
        return result.get ();
    }
    
    void using_yield_ec(asio::yield_context yield) {
        for (bool success : { true, false }) {
            boost::system::error_code ec;
            auto answer = async_meaning_of_life(success, yield[ec]);
            std::cout << __FUNCTION__ << ": Result: " << ec.message() << "\n";
            std::cout << __FUNCTION__ << ": Answer: " << answer << "\n";
        }
    }
    
    void using_yield_catch(asio::yield_context yield) {
        for (bool success : { true, false }) 
        try {
            auto answer = async_meaning_of_life(success, yield);
            std::cout << __FUNCTION__ << ": Answer: " << answer << "\n";
        } catch(boost::system::system_error const& e) {
            std::cout << __FUNCTION__ << ": Caught: " << e.code().message() << "\n";
        }
    }
    
    void using_future() {
        for (bool success : { true, false }) 
        try {
            auto answer = async_meaning_of_life(success, asio::use_future);
            std::cout << __FUNCTION__ << ": Answer: " << answer.get() << "\n";
        } catch(boost::system::system_error const& e) {
            std::cout << __FUNCTION__ << ": Caught: " << e.code().message() << "\n";
        }
    }
    
    void using_handler() {
        for (bool success : { true, false })
            async_meaning_of_life(success, [](error_code ec, int answer) {
                std::cout << "using_handler: Result: " << ec.message() << "\n";
                std::cout << "using_handler: Answer: " << answer << "\n";
            });
    }
    
    int main() {
        asio::io_service svc;
    
        spawn(svc, using_yield_ec);
        spawn(svc, using_yield_catch);
        std::thread work([] {
                using_future();
                using_handler();
            });
    
        svc.run();
        work.join();
    }
    

    打印:

    using_yield_ec: Result: Success
    using_yield_ec: Answer: 42
    using_yield_ec: Result: Operation canceled
    using_yield_ec: Answer: 0
    using_future: Answer: 42
    using_yield_catch: Answer: 42
    using_yield_catch: Caught: Operation canceled
    using_future: Caught: Operation canceled
    using_handler: Result: Success
    using_handler: Answer: 42
    using_handler: Result: Operation canceled
    using_handler: Answer: 0
    

    注意:为简单起见,我没有添加输出同步,因此输出可能会根据运行时执行顺序而混合


    ¹ 参见例如这个很好的演示如何使用它来扩展你自己的异步结果模式库boost::asio with boost::unique_future

    【讨论】:

    • 感谢@sehe,但这是我在问题中提到的“将yield 对象转换为asio::handler”解决方案。我不是想创建一个支持处理程序、期货和协程的通用异步函数。恰恰相反,为了代码清晰,我试图尽可能长时间地使用协程。
    • 认为这个答案证实了你的恐惧吗?在有人提供新信息(或库 API 得到扩展)之前,您想要做的是保持尴尬。
    • 抱歉耽搁了。我认为,如果您修改答案以在开头明确提及目前不可能的地方,我会很乐意将其标记为答案,直到有人证明我们错了。
    • 这在 boost 1.66 中不起作用,会出现编译错误。
    • @sehe 太棒了,非常感谢。你让我开心;-)
    猜你喜欢
    • 2015-03-14
    • 2015-01-15
    • 1970-01-01
    • 1970-01-01
    • 2020-06-12
    • 2012-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多