Резюме ASIO Stackless Coroutine

Немного поиграв с текущей реализацией 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;
}

Спасибо за вашу помощь

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;
}

Обратите внимание, что невозможно получить доход от подфункций. Это ограничение сопрограмм без стеков.

Как это устроено:

  • yield хранит уникальный идентификатор для перехода в сопрограмму
  • yield будет запускать выражение, которое вы помещаете за ним, должен быть асинхронный вызов или будет мало пользы
  • после запуска он вырвется из блока повторного входа.

Теперь «запуск» завершен, и вы запускаете другой поток для ожидания. Тем временем поток foo заканчивает свой сон и вызывает вашу задачу снова. Сейчас:

  • блок повторного ввода будет читать состояние вашей сопрограммы, чтобы найти, что он должен перепрыгнуть через вызов foo
  • Ваша задача возобновится, распечатает результат и выпадет из функции, вернувшись к потоку foo.

Поток foo завершен, и main, вероятно, все еще ожидает второй поток.

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]