Резюме 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, вероятно, все еще ожидает второй поток.

Другие вопросы по тегам