Немного поиграв с текущей реализацией Coroutine TS в Clang, я наткнулся на реализацию сопрограммы asio без стеков. Они описаны как Портативные стекированные сопрограммы в одном * заголовке.
Имея дело в основном с асинхронным кодом, я тоже хотел попробовать их.
Блок сопрограммы внутри main
функция должна ожидать результата, асинхронно установленного потоком, созданным в функции foo
, Однако я не уверен, как продолжить выполнение <1>
(после yield
выражение), как только поток установил значение.
Используя Coroutine TS, я бы назвал coroutine_handle
, тем не мение boost::asio::coroutine
кажется не вызываемым.
Это возможно даже с помощью boost::asio::coroutine
?
#include <thread>
#include <chrono>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/yield.hpp>
#include <cstdio>
using namespace std::chrono_literals;
using coroutine = boost::asio::coroutine;
void foo(coroutine & coro, int & result) {
std::thread([&](){
std::this_thread::sleep_for(1s);
result = 3;
// how to resume at <1>?
}).detach();
}
int main(int, const char**) {
coroutine coro;
int result;
reenter(coro) {
// Wait for result
yield foo(coro, result);
// <1>
std::printf("%d\n", result);
}
std::thread([](){
std::this_thread::sleep_for(2s);
}).join();
return 0;
}
Спасибо за вашу помощь
Прежде всего, сопрограммы без стеков лучше описываются как возобновляемые функции. Проблема, с которой вы столкнулись в данный момент, заключается в использовании main Если вы извлечете свою логику в отдельный функтор, это будет возможно:
class task; // Forward declare both because they should know about each other
void foo(task &task, int &result);
// Common practice is to subclass coro
class task : coroutine {
// All reused variables should not be local or they will be
// re-initialized
int result;
void start() {
// In order to actually begin, we need to "invoke ourselves"(*this)();
}
// Actual task implementation
void operator()() {
// Reenter actually manages the jumps defined by yield
// If it's executed for the first time, it will just run from the start
// If it reenters (aka, yield has caused it to stop and we re-execute)
// it will jump to the right place for you
reenter(this) {
// Yield will store the current location, when reenter
// is ran a second time, it will jump past yield for you
yield foo(*this, result);
std::printf("%d\n", result)
}
}
}
// Our longer task
void foo(task & t, int & result) {
std::thread([&](){
std::this_thread::sleep_for(1s);
result = 3;
// The result is done, reenter the task which will go to just after yield
// Keep in mind this will now run on the current thread
t();
}).detach();
}
int main(int, const char**) {
task t;
// This will start the task
t.start();
std::thread([](){
std::this_thread::sleep_for(2s);
}).join();
return 0;
}
Обратите внимание, что невозможно получить доход от подфункций. Это ограничение сопрограмм без стеков.
Как это устроено:
Теперь «запуск» завершен, и вы запускаете другой поток для ожидания. Тем временем поток foo заканчивает свой сон и вызывает вашу задачу снова. Сейчас:
Поток foo завершен, и main, вероятно, все еще ожидает второй поток.
Других решений пока нет …