Exception throw in boost::asio::spawn not caught by try catch

≡放荡痞女 提交于 2021-01-28 05:14:31

问题


In this convoluted example, two for loops are started by boost::asio::spawn() asynchronously. The first for loop prints an odd number every 1000us and the second one prints an even number every 1000us.

I expect the output to be something like 1 2 3 4 5 6 and then the 'Throw an error' message should be printed to stderr by the call to cerr.

However, the exception is actually thrown in loop.run() so it is not caught by the try catch block.

Can someone point out how to properly catch the runtime_error when calling spawn()? Does spawn() not reraise the exception thrown in the spawned coroutine to its parent scope?

using namespace std;
using namespace boost::asio;
using boost::posix_time::microseconds;

io_service loop;
spawn(loop, [&loop](yield_context yield)
{
    try
    {
        spawn(loop, [&loop](yield_context yield)
        {
            deadline_timer timer{loop};
            for(unsigned i = 0; i < 3; ++i)
            {
                cout << i * 2 + 1 << endl;
                timer.expires_from_now(microseconds(1000));
                timer.async_wait(yield);
            }
            throw runtime_error("Throw an error");
        });

        spawn(loop, [&loop](yield_context yield)
        {
            deadline_timer timer{loop};
            for(unsigned i = 0; i < 3; ++i)
            {
                cout << (i + 1) * 2 << endl;
                timer.expires_from_now(microseconds(1000));
                timer.async_wait(yield);
            }
        });
    } catch(const runtime_error& ex)
    {
        cerr << ex.what() << endl;
    }
});

loop.run();

回答1:


All handlers are invoked by the service loop, meaning you always need to handle errors: Should the exception thrown by boost::asio::io_service::run() be caught?

Sidenote: In the case of coroutines, it's my experience that catching by reference is a bit tricky. This more than likely has to do with the lifetime of the coroutine stack itself.

You can demonstrate it using:

while (true) {
    try {
        loop.run();
        break;
    } catch(std::runtime_error ex) {
        std::cerr << "L:" << __LINE__ << ": " << ex.what() << "\n";
    }
}

Other Notes

Note that passing error_code errors can be done in two ways across idiomatic Asio interfaces, including Coroutines: How to set error_code to asio::yield_context

Full Sample

Simply adapting your sample to be selfcontained:

Live On Coliru

#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/high_resolution_timer.hpp>

using namespace std::chrono_literals;

int main() {
    boost::asio::io_service loop;

    spawn(loop, [&loop](boost::asio::yield_context yield)
    {
        try
        {
            spawn(yield, [&loop](boost::asio::yield_context yield)
            {
                boost::asio::high_resolution_timer timer{loop};
                for(unsigned i = 0; i < 3; ++i)
                {
                    std::cout << i * 2 + 1 << std::endl;
                    timer.expires_from_now(100ms);
                    timer.async_wait(yield);
                }
                throw std::system_error(ENOENT, std::system_category(), "Throw an error");
                //throw boost::system::system_error(ENOENT, boost::system::system_category(), "Throw an error");
            });

            spawn(yield, [&loop](boost::asio::yield_context yield)
            {
                boost::asio::high_resolution_timer timer{loop};
                for(unsigned i = 0; i < 3; ++i)
                {
                    std::cout << (i + 1) * 2 << std::endl;
                    timer.expires_from_now(100ms);
                    timer.async_wait(yield);
                }
            });
        } catch(const std::runtime_error& ex)
        {
            std::cerr << "L:" << __LINE__ << ": " << ex.what() << "\n";
        }
    });

    while (true) {
        try {
            loop.run();
            break;
        } catch(std::runtime_error ex) {
            std::cerr << "L:" << __LINE__ << ": " << ex.what() << "\n";
        }
    }
}

Prints

1
2
3
4
5
6
L:49: Throw an error: No such file or directory

BONUS:

Doing the extra approach with generalized competion token and async_result:

Live On Coliru

#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/high_resolution_timer.hpp>

using namespace std::chrono_literals;

boost::asio::io_service loop;

template <typename Token>
auto async_foo(bool success, Token&& token)
{
    typename boost::asio::handler_type<Token, void(boost::system::error_code, int)>::type
                 handler (std::forward<Token> (token));

    boost::asio::async_result<decltype (handler)> result (handler);

    boost::asio::yield_context yield(token);

    boost::asio::high_resolution_timer timer{loop};
    for(unsigned i = 0; i < 3; ++i) {
        std::cout << (i * 2 + (success?0:1)) << std::endl;
        timer.expires_from_now(100ms);
        timer.async_wait(yield);
    }

    if (success)
        handler(42);
    else
        throw boost::system::system_error(ENOENT, boost::system::system_category(), "Throw an error");

    return result.get();
}

int main() {

    auto spawn_foo = [](bool success) {
        spawn(loop, [=](boost::asio::yield_context yield) {
            try
            {
                int answer = async_foo(success, yield);
                std::cout << "async_foo returned " << answer << std::endl;
            } catch(const std::runtime_error& ex)
            {
                std::cerr << "L:" << __LINE__ << ": " << ex.what() << std::endl;
            }
        });
    };

    spawn_foo(true);
    spawn_foo(false);

    loop.run();
}

Prints

0
1
2
3
4
5
async_foo returned 42
L:45: Throw an error: No such file or directory


来源:https://stackoverflow.com/questions/47953926/exception-throw-in-boostasiospawn-not-caught-by-try-catch

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!