我想按照here所述为boost :: asio编写自定义异步函数。
但是我在 result.get
上获得了boost :: coroutines :: detail :: forced_unwind异常#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 enter\n";
my_timer (timer, yield);
std::cout << "my_timer returns\n";
}
catch (const boost::coroutines::detail::forced_unwind& e)
{
std::cout << "boost::coroutines::detail::forced_unwind\n";
}
}
);
io.run ();
}
上的相同代码
更新:
行为存在于:
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)
答案 0 :(得分:9)
简而言之,您需要创建一个处理程序副本,例如将其发布到io_service
,然后再尝试获取async_result
以保持协程生效。
Boost.Asio通过破坏协程来防止不可恢复的协程无限期地暂停,导致协程的堆栈展开。协程对象将在其销毁期间抛出boost::coroutines::detail::forced_unwind
,导致挂起的堆栈展开。 Asio通过以下方式实现了这一目标:
yield_context
CompletionToken为协程维护weak_ptr
。 handler_type::type
处理程序时,它会通过CompletionToken的shared_ptr
获取协程的weak_ptr
。当处理程序作为完成处理程序传递给异步操作时,将复制处理程序及其shared_ptr
。调用处理程序时,它将恢复协程。async_result::get()
时,专门化将重置在构造期间传递给shared_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
要解决此问题,需要在需要恢复协程时通过asio_handler_invoke()
复制和调用handler
。例如,以下内容将完成处理程序 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 ();
如所示here,使用此附加代码,输出变为:
my_timer enter
my_timer returns
<子> 1。完成处理程序代码可能会被清理一下,但在我回答how to resume a Boost.Asio stackful coroutine from a different thread时,我发现有些编译器选择了错误的asio_handler_invoke
挂钩。
答案 1 :(得分:4)
这是一个Boost Coroutine实现细节。
如此处所述: exceptions
⚠重要
协同函数执行的代码不得阻止
detail::forced_unwind exception
的传播。吸收该异常将导致堆栈展开失败。因此,任何捕获所有异常的代码都必须重新throw
任何待处理的detail::forced_unwind
异常。
因此,您明确必需通过此异常。明确地将处理程序编码为:
<强> Live On Coliru 强>
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";
}
这个特殊的例外是一个实现细节,必须
公平地说,这使得“天真地”不安全。使用可能无法提供此保证的现有(传统)代码。
我认为这是非常有力的理由集中式异常策略(例如 using a Lippincott function用于异常处理程序)
请注意,Coroutines中也可能明确禁止最后一个想法:
⚠重要
不要从catch块内跳转,而是在另一个执行上下文中重新抛出异常。
更新:由于@DeadMG刚评论了这篇文章,我们可以将Lippincott函数简单地转换为包装函数,它可以在集中异常处理时满足Coroutine的要求。
< / LI>