Каков наилучший способ создания конечного автомата с использованием Boost SML для управления объектами и потоками ASIO? - PullRequest
0 голосов
/ 01 ноября 2019

Прежде всего, я хотел бы уточнить, что под объектами ASIO я имею в виду простой таймер, но в дальнейшем я хочу создать конечный автомат для работы с сокетами и передачей данных.

Я изучаю библиотеку Boost SML уже несколько недель и пробую разные вещи. Мне это очень нравится, однако документация не помогает в моем случае использования, и ее источник не совсем привлекателен для кого-то еще довольно новичка в метапрограммировании.

В настоящее время я хотел бы создать состояниемашина, которая управляет таймером ASIO (для асинхронного ожидания). Интерфейс будет обеспечивать стартовый вызов, где вы можете сказать, как долго он должен ждать, отменить вызов (чтобы отменить текущее ожидание) и некоторый обратный вызов, который будет вызван при срабатывании таймера.

Я уже достигэто одним способом, следуя обоим примерам sml в этом репозитории , и это работает хорошо - у меня есть класс, который управляет таймером и содержит конечный автомат. Открытый интерфейс предоставляет средства для ввода соответствующих событий в FSM и запроса его состояния. Частный интерфейс предоставляет средства для запуска и остановки таймера. Класс FSM является другом контроллера, поэтому он имеет доступ к закрытым функциям.

Однако мне было интересно, есть ли способ взять некоторые функции контроллера и перенести их в FSM - он будет удерживатьвсе объекты ASIO и запустить io_context / io_service в потоке, который он породил.

(1) Первая проблема, с которой я столкнусь, заключается в том, что конечный автомат копируется - объекты ASIO этого не позволяют, ноэто можно обойти, обернув их в общие указатели.

(2) Далее, отправка событий в FSM изнутри. Я понял, как сделать это из действий, получив вызываемый boost :: sml :: back :: process <> объект и используя его для «отправки» события в очередь, но это было бы бесполезноиз обработчика ASIO, так как это по умолчанию не будет вызываться из действия. Я полагаю, что способ обойти это - захватить вызываемое в обработчик таймера с помощью лямбды, например:

// Some pseudo code (this would all be done in one class):

// Transition table
"idle"_s + event<Wait> / start_waiting = "waiting"_s

// Events
struct Wait { std::chrono::seconds duration; };
struct Cancel {};
struct Expire {};

// Actions
std::function<void(Wait const&, boost::sml::back::process<Cancel, Expire>)> start_waiting =
  [this] (Wait const& e, boost::sml::back::process<Cancel, Expire> p) {
    run_timer(e, p);
};

// Private function
void run_timer(Wait const& e, boost::sml::back::process<Cancel, Expire>& p) {
  m_timer->expires_after(e.duration);

  auto timerHandler = [&p] (asio::error_code const& e) {
    if (e == asio::error::operation_aborted)
      p(Cancel{});
    else
      p(Expire{});
  };

  timer->async_wait(timerHandler);
}

Но это похоже на что-то нехорошее.

(3)Последнее, что меня беспокоит, это то, как конечный автомат будет обрабатывать потоки. Очевидно, что обработчик таймера будет выполняться в своем собственном потоке. Если это отправляет событие в очередь FSM, будет ли это событие обрабатываться тем же потоком, который его опубликовал? Я предполагаю, да, потому что я не вижу никаких упоминаний о потоках (кроме безопасности потоков) в заголовке. Это будет диктовать, как я буду управлять временем жизни потоков.

Любые советы по альтернативным способам построения этого, а также их плюсы и минусы, будут очень полезны.

...