boost::asio:: yield контекст: неожиданное принудительное исключение размотки
Я пытаюсь написать свою пользовательскую асинхронную функцию для boost:: asio, как описано здесь.
однако я получаю boost:: coroutines:: detail:: forced_unwind исключение на линии с результат.get
#include <boost/chrono.hpp>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
#include <iostream>
namespace asio = ::boost::asio;
template <typename Timer, typename Token>
auto my_timer (Timer& timer, Token&& token)
{
typename asio::handler_type<Token,
void (::boost::system::error_code const)>::type
handler (std::forward<Token> (token));
asio::async_result<decltype (handler)> result (handler);
timer.async_wait (handler);
return result.get (); // Got forced_unwind exception here.
}
int main ()
{
asio::io_service io;
asio::steady_timer timer (io, ::boost::chrono::seconds (1));
asio::spawn (io, [&] (asio::yield_context yield)
{
try {
std::cout << "my_timer entern";
my_timer (timer, yield);
std::cout << "my_timer returnsn";
}
catch (const boost::coroutines::detail::forced_unwind& e)
{
std::cout << "boost::coroutines::detail::forced_unwindn";
}
}
);
io.run ();
}
тот же код Coliru
обновление:
поведение существует на:
Darwin 14.0.0 (MacOS 10.10)
clang version 3.6.0 (trunk 216817) and gcc version 4.9.1 (MacPorts gcc49 4.9.1_1)
boost 1.57
и
Red Hat 6.5
gcc version 4.7.2 20121015 (Red Hat 4.7.2-5) (GCC)
boost 1.57 and 1.56
(the example code was trivially modified because gcc 4.7 does not support c++14 mode)
2 ответов
короче говоря, вам нужно создать экземпляр обработчика, например, разместив его в io_service
, прежде чем пытаться получить async_result
для того, чтобы сохранить корутину в живых.
импульс.Asio предотвращает невозобновляемую корутину от бесконечного приостановления, уничтожая корутин, в результате чего стек корутина разматывается. Объект coroutine будет бросать boost::coroutines::detail::forced_unwind
во время его разрушение, вызывающие приостановлено стека. Asio выполняет это by:
- на
yield_context
CompletionToken поддерживаетweak_ptr
в сопрограммы. - когда специализированной
handler_type::type
обработчик построен, он получаетshared_ptr
для coroutine через CompletionToken вweak_ptr
. Когда обработчик передается как обработчик завершения асинхронных операций, обработчик и егоshared_ptr
копируются. Когда вызывается обработчик, он возобновляет работу сопрограммы. - при вызове
async_result::get()
, специализация сбросит coroutineshared_ptr
принадлежит обработчику, который был переданasync_result
во время конструкции, и после этого дайте корутину.
вот попытка проиллюстрировать выполнение кода. Пути в |
укажите активный стек,:
указывает на подвешенный стек, а стрелки используются для указания передачи управления:
boost::asio::io_service io_service;
boost::asio::spawn(io_service, &my_timer);
`-- dispatch a coroutine creator
into the io_service.
io_service.run();
|-- invoke the coroutine entry
| handler.
| |-- create coroutine
| | (count: 1)
| |-- start coroutine ----> my_timer()
: : |-- create handler1 (count: 2)
: : |-- create asnyc_result1(handler1)
: : |-- timer.async_wait(handler)
: : | |-- create handler2 (count: 3)
: : | |-- create async_result2(handler2)
: : | |-- create operation and copy
: : | | handler3 (count: 4)
: : | `-- async_result2.get()
: : | |-- handler2.reset() (count: 3)
| `-- return <---- | `-- yield
| `-- ~entry handler :
| (count: 2) :
|-- io_service has work (the :
| async_wait operation) :
| ...async wait completes... :
|-- invoke handler3 :
| |-- resume ----> |-- async_result1.get()
: : | |-- handler1.reset() (count: 1)
| `-- return <---- | `-- yield
| `-- ~handler3 : :
| | (count: 0) : :
| `-- ~coroutine() ----> | `-- throw forced_unwind
исправить эту проблему, handler
необходимо скопировать и вызывается через asio_handler_invoke()
когда пришло время возобновить работу корутины. Например, следующее сообщение будет содержать обработчик завершения1 на io_service
это вызывает копию handler
:
timer.async_wait (handler);
timer.get_io_service().post(
std::bind([](decltype(handler) handler)
{
boost::system::error_code error;
// Handler must be invoked through asio_handler_invoke hooks
// to properly synchronize with the coroutine's execution
// context.
using boost::asio::asio_handler_invoke;
asio_handler_invoke(std::bind(handler, error), &handler);
}, handler)
);
return result.get ();
как показала здесь, С этим дополнительным кодом, выход становится:
my_timer enter
my_timer returns
1. Код обработчика завершения, вероятно, может быть немного очищен, но как я отвечал как возобновить импульс.Асио stackful coroutine из другой нити, я заметил, что некоторые компиляторы выбирают неправильный asio_handler_invoke
крюк.
это деталь реализации Boost Coroutine.
как описано здесь: исключения
Important важно
код, выполняемый функцией coroutine, не должен препятствовать распространению
detail::forced_unwind exception
. Поглощение этого исключения приведет к сбою размотки стека. Таким образом, любой код, который ловит все исключения, должен re-throw
любой на рассмотренииdetail::forced_unwind
исключения.
так, ты явно требуются чтобы передать это исключение. Явно Закодируйте обработчик, например:
try {
std::cout << "my_timer enter\n";
my_timer(timer, yield);
std::cout << "my_timer returns\n";
}
catch (boost::coroutines::detail::forced_unwind const& e)
{
throw; // required for Boost Coroutine!
}
catch (std::exception const& e)
{
std::cout << "exception '" << e.what() << "'\n";
}
это конкретное исключение является детализацией реализации и должно
- ожидается в контексте coroutine
- не проглатывается, или семантика RAII будет нарушена, что приведет к утечкам ресурсов и, возможно, неопределенному поведению с вашим RAII тип.
справедливости ради, это делает небезопасным "наивно" использовать существующий (устаревший) код, который может не позволить себе эту гарантию. Я думаю, что это очень сильная причина для
- руководящие принципы против неспецифических уловов, за исключением голой rethrow
-
централизованные стратегии исключений (например,использование функции Липпинкотта для обработчиков исключений)
остерегайтесь, что последняя идея может быть прямо запрещено в Coroutines тоже:
Important важно
Не прыгайте изнутри блока catch и не повторяйте исключение в другом контексте выполнения.
обновление: как @DeadMG только что прокомментировал эту статью, мы можем тривиально преобразовать функцию Липпинкотта в функцию обертывания, которая может удовлетворять требованиям для Coroutine при централизации обработки исключений.