如何在其链的上下文中恢复堆栈式协程的执行?
using Yield = asio::yield_context;
using boost::system::error_code;
int Func(Yield yield) {
error_code ec;
asio::detail::async_result_init<Yield, void(error_code, int)> init(yield[ec]);
std::thread th(std::bind(Process, init.handler));
int result = init.result.get(); // <--- yield at here
return result;
}
如何实现 Process
以便 Func
将在 Func
最初产生的链的上下文中恢复?
How to implement Process
so that Func
will resumed in the context of the strand that Func
was originally spawned on?
推荐答案
Boost.Asio 使用了一个辅助函数,asio_handler_invoke
,为调用策略提供自定义点.例如,当 Handler 被 strand
包裹时,调用策略将导致在调用时通过 strand
调度处理程序.如文档中所述,asio_handler_invoke
应该通过依赖于参数的查找来调用.
Boost.Asio uses a helper function, asio_handler_invoke
, to provide a customization point for invocation strategies. For example, when a Handler has been wrapped by a strand
, the invocation strategy will cause the handler to be dispatched through the strand
upon invocation. As noted in the documentation, asio_handler_invoke
should be invoked via argument-dependent lookup.
using boost::asio::asio_handler_invoke;
asio_handler_invoke(nullary_functor, &handler);
<小时>
对于堆栈式协程,在产生协程和调用 handler_type
与 yield_context
恢复协程:
- 如果代码当前正在协程中运行,则它位于与协程关联的
strand
内.本质上,一个简单的处理程序由恢复协程的strand
包裹,导致执行跳转到协程,阻塞当前在strand
中的处理程序.当协程让步时,执行会跳回到strand
处理程序,让它完成. - 虽然
spawn()
向io_service
(一个将启动并跳转到协程的处理程序)添加工作,协程本身不起作用.为了防止io_service
事件循环在协程未完成时结束,可能需要在让步之前向io_service
添加工作. - Stackful 协程使用
strand
来帮助保证协程在 resume 被调用之前yields.Asio 1.10.6/Boost 1.58 启用能够从启动函数中安全地调用完成处理程序.之前的版本要求完成处理程序不能从启动函数中调用,因为它的调用策略将dispatch()
,导致协程在被挂起之前尝试恢复.
- If code is currently running in the coroutine, then it is within the
strand
associated with the coroutine. Essentially, a simple handler is wrapped by thestrand
that resumes the coroutine, causing execution to jump to the coroutine, blocking the handler currently in thestrand
. When the coroutine yields, execution jumps back to thestrand
handler, allowing it to complete. - While
spawn()
adds work to theio_service
(a handler that will start and jump to the coroutine), the coroutine itself is not work. To prevent theio_service
event loop from ending while a coroutine is outstanding, it may be necessary to add work to theio_service
before yielding. - Stackful coroutines use a
strand
to help guarantee the coroutine yields before resume is invoked. Asio 1.10.6 / Boost 1.58 enabled being able to safely invoke the completion handler from within the initiating function. Prior versions required that the completion handler was not invoked from within the initiating function, as its invocation strategy woulddispatch()
, causing the coroutine to attempt resumption before being suspended.
这是一个完整的示例,其中说明了这些细节:
Here is a complete example that accounts for these details:
#include <iostream> // std::cout, std::endl
#include <chrono> // std::chrono::seconds
#include <functional> // std::bind
#include <thread> // std::thread
#include <utility> // std::forward
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
template <typename CompletionToken, typename Signature>
using handler_type_t = typename boost::asio::handler_type<
CompletionToken, Signature>::type;
template <typename Handler>
using async_result = boost::asio::async_result<Handler>;
/// @brief Helper type used to initialize the asnyc_result with the handler.
template <typename CompletionToken, typename Signature>
struct async_completion
{
typedef handler_type_t<CompletionToken, Signature> handler_type;
async_completion(CompletionToken&& token)
: handler(std::forward<CompletionToken>(token)),
result(handler)
{}
handler_type handler;
async_result<handler_type> result;
};
template <typename Signature, typename CompletionToken>
typename async_result<
handler_type_t<CompletionToken, Signature>
>::type
async_func(CompletionToken&& token, boost::asio::io_service& io_service)
{
// The coroutine itself is not work, so guarantee the io_service has
// work.
boost::asio::io_service::work work(io_service);
// Initialize the async completion handler and result.
async_completion<CompletionToken, Signature> completion(
std::forward<CompletionToken>(token));
auto handler = completion.handler;
std::cout << "Spawning thread" << std::endl;
std::thread([](decltype(handler) handler)
{
// The handler will be dispatched to the coroutine's strand.
// As this thread is not running within the strand, the handler
// will actually be posted, guaranteeing that yield will occur
// before the resume.
std::cout << "Resume coroutine" << std::endl;
using boost::asio::asio_handler_invoke;
asio_handler_invoke(std::bind(handler, 42), &handler);
}, handler).detach();
// Demonstrate that the handler is serialized through the strand by
// allowing the thread to run before suspending this coroutine.
std::this_thread::sleep_for(std::chrono::seconds(2));
// Yield the coroutine. When this yields, execution transfers back to
// a handler that is currently in the strand. The handler will complete
// allowing other handlers that have been posted to the strand to run.
std::cout << "Suspend coroutine" << std::endl;
return completion.result.get();
}
int main()
{
boost::asio::io_service io_service;
boost::asio::spawn(io_service,
[&io_service](boost::asio::yield_context yield)
{
auto result = async_func<void(int)>(yield, io_service);
std::cout << "Got: " << result << std::endl;
});
std::cout << "Running" << std::endl;
io_service.run();
std::cout << "Finish" << std::endl;
}
输出:
Running
Spawning thread
Resume coroutine
Suspend coroutine
Got: 42
Finish
<小时>
有关更多详细信息,请考虑阅读 图书馆基金会异步操作.它提供了关于异步操作的组成、Signature
如何影响 async_result
以及 async_result
、handler_type<的整体设计的更多细节/code> 和
async_completion
.
For much more details, please consider reading Library Foundations for
Asynchronous Operations. It provides much greater detail into the composition of asynchronous operations, how Signature
affects async_result
, and the overall design of async_result
, handler_type
, and async_completion
.
相关文章