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
最初产生的链的上下文中恢复?
答案 0 :(得分:9)
Boost.Asio使用辅助函数asio_handler_invoke
为调用策略提供自定义点。例如,当Handler被strand
包装时,调用策略将导致在调用时通过strand
调度处理程序。如documentation中所述,asio_handler_invoke
应该通过参数依赖查找来调用。
using boost::asio::asio_handler_invoke;
asio_handler_invoke(nullary_functor, &handler);
对于堆栈协程,在生成协程时以及在调用与handler_type
关联的yield_context
以恢复协程时,需要考虑各种重要细节:
strand
范围内。本质上,一个简单的处理程序由strand
包装,它恢复协程,导致执行跳转到协程,阻塞当前strand
中的处理程序。当协同程序产生时,执行会跳回strand
处理程序,允许它完成。spawn()
将工作添加到io_service
(将启动并跳转到协程的处理程序),但协程本身不起作用。为了防止{/ 1}}事件循环在协程未完成时结束,可能需要在屈服之前将工作添加到io_service
。io_service
来帮助保证协程产生。 Asio 1.10.6 / Boost 1.58能够从启动函数中安全地调用完成处理程序。先前版本要求未在启动函数内调用完成处理程序,因为其调用策略为dispatch()
,导致协程在被挂起之前尝试恢复。这是一个完整的example,说明了这些细节:
strand
输出:
#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;
}
有关详情,请考虑阅读Library Foundations for
Asynchronous Operations。它提供了有关异步操作组合的更多详细信息,Running
Spawning thread
Resume coroutine
Suspend coroutine
Got: 42
Finish
如何影响Signature
以及async_result
,async_result
和handler_type
的整体设计。< / p>
答案 1 :(得分:4)
以下是基于Tanner的优秀答案的Boost 1.66.0的更新示例:
#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 Signature, typename CompletionToken>
auto async_add_one(CompletionToken token, int value) {
// Initialize the async completion handler and result
// Careful to make sure token is a copy, as completion's handler takes a reference
using completion_type = boost::asio::async_completion<CompletionToken, Signature>;
completion_type completion{ token };
std::cout << "Spawning thread" << std::endl;
std::thread([handler = completion.completion_handler, value]() {
// 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;
// separate using statement is important
// as asio_handler_invoke is overloaded based on handler's type
using boost::asio::asio_handler_invoke;
asio_handler_invoke(std::bind(handler, value + 1), &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_context io_context;
boost::asio::spawn(
io_context,
[&io_context](boost::asio::yield_context yield) {
// Here is your coroutine
// The coroutine itself is not work, so guarantee the io_context
// has work while the coroutine is running
const auto work = boost::asio::make_work_guard(io_context);
// add one to zero
const auto result = async_add_one<void(int)>(yield, 0);
std::cout << "Got: " << result << std::endl; // Got: 1
// add one to one forty one
const auto result2 = async_add_one<void(int)>(yield, 41);
std::cout << "Got: " << result2 << std::endl; // Got: 42
}
);
std::cout << "Running" << std::endl;
io_context.run();
std::cout << "Finish" << std::endl;
}
输出:
Running
Spawning thread
Resume coroutine
Suspend coroutine
Got: 1
Spawning thread
Resume coroutine
Suspend coroutine
Got: 42
Finish
说明:
completion.result.get
)产生的结果将使相关的CompletionToken放弃其基础强引用。这最终会导致意外提前终止协程。using boost::asio::asio_handler_invoke
声明非常重要。显式调用可以防止调用正确的重载。-
我还要提到我们的应用程序最终得到了两个协同程序可以与之交互的io_context。特别是一个用于I / O绑定工作的上下文,另一个用于CPU。使用带有boost::asio::spawn
的显式链最终使我们能够很好地控制协程将运行/恢复的上下文。这有助于我们避免零星的BOOST_ASSERT(!is_running())失败。
使用显式链创建一个协同程序:
auto strand = std::make_shared<strand_type>(io_context.get_executor());
boost::asio::spawn(
*strand,
[&io_context, strand](yield_context_type yield) {
// coroutine
}
);
调用显式调度到strand(multi io_context world):
boost::asio::dispatch(*strand, [handler = completion.completion_handler, value] {
using boost::asio::asio_handler_invoke;
asio_handler_invoke(std::bind(handler, value), &handler);
});
-
我们还发现,在async_result签名中使用future将允许在恢复时将异常传播回协程。
using bound_function = void(std::future<RETURN_TYPE>);
using completion_type = boost::asio::async_completion<yield_context_type, bound_function>;
产量为:
auto future = completion.result.get();
return future.get(); // may rethrow exception in your coroutine's context
答案 2 :(得分:0)
通过Boost Asio提供的执行程序框架创建线程,使事情变得复杂。
出于这个原因,你不应该假设你想要的是可能的。我强烈建议您只需向io_service
添加更多主题,然后让它为您管理主题。
或者,您可以扩展库并添加您显然想要的新功能。如果是这样,最好联系开发人员邮件列表获取建议。也许他们欢迎这个功能¹?
¹(有趣的是,你没有描述,所以我不会问它的目的是什么)
答案 3 :(得分:0)
using CallbackHandler = boost::asio::handler_type<Yield, void (error_code, int)>::type;
void Process(CallbackHandler handler) {
int the_result = 81;
boost::asio::detail::asio_handler_invoke(
std::bind(handler, error_code(), the_result), &handler);
}
@sehe暗示,我制定了上述工作解决方案。但我不确定这是否是正确/惯用/最佳方式。欢迎发表评论/编辑此答案。