Как спроектировать конечный автомат для неблокирующего ввода-вывода?

Я использую инфраструктуру Qt, которая по умолчанию имеет неблокирующий ввод-вывод для разработки приложения, перемещающегося по нескольким веб-страницам (интернет-магазинам) и выполняющего различные действия на этих страницах. Я «сопоставляю» конкретную веб-страницу с конечным автоматом, который использую для навигации по этой странице.
Этот конечный автомат имеет следующие переходы:
Connect, LogIn, Query, LogOut, Disconnect
и эти состояния:
Start, Connecting, Connected, LoggingIn, LoggedIn, Querying, QueryDone, LoggingOut, LoggedOut, Disconnecting, Disconnected
Переходы из состояния *ing в состояние *ed (Connecting->Connected) происходят из-за LoadFinished асинхронных сетевых событий, полученных от сетевого объекта при загрузке текущего запрошенного URL-адреса. Переходы из состояния *ed в состояние *ing (Connected->LoggingIn) происходят из-за событий, отправляемых мной.
Я хочу иметь возможность отправлять несколько событий (команд) на этот компьютер (например, Connect, LogIn, Query("productA"), Query("productB"), LogOut, LogIn, Query("productC"), LogOut, Disconnect) сразу и обработать их. Я не хочу блокировать ожидание, пока компьютер завершит обработку всех событий, которые я ему отправил. Проблема в том, что они должны чередоваться с вышеупомянутыми сетевыми событиями, информирующими машину о загружаемом URL-адресе. Без чередования машина не может изменить свое состояние (и обработать мои события), потому что переход от *ing к *ed происходит только после получения события сетевого типа.

Как я могу достичь своей дизайнерской цели?

ИЗМЕНИТЬ

  1. Конечный автомат, который я использую, имеет свой собственный цикл событий, и события не ставятся в очередь в нем, поэтому машина может пропустить их, если они приходят, когда машина занята.
  2. События сетевого ввода-вывода не отправляются напрямую ни в конечный автомат, ни в очередь событий, которую я использую. Они публикуются в моем коде (обработчике), и я должен их обрабатывать. Я могу переслать их по своему желанию, но, пожалуйста, имейте в виду примечание №. 1.
  3. Взгляните на мой ответ на этот вопрос, где я подробно описал свой текущий дизайн. Вопрос в том, могу ли я улучшить этот дизайн, сделав его

    • More robust
    • Проще

person Piotr Dobrogost    schedule 12.08.2009    source источник


Ответы (6)


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

Это означает, что любая логика, предполагающая использование результата одного перехода в следующем, должна быть в машине. Например, если на странице «вход завершен» указано, куда идти дальше. Если это невозможно, то событие, возможно, может включать обратный вызов, который машина может вызвать, чтобы вернуть все, что ей нужно знать.

person Steve Jessop    schedule 12.08.2009
comment
Хорошее предложение, но оно не решает проблему. Если я поставлю в очередь свои события, машина застрянет в ожидании сетевого события... - person Piotr Dobrogost; 12.08.2009
comment
О, если вы не имеете в виду, что сетевые события застряли бы в очереди за событиями, которые выдает клиентский код. В этом случае вам нужна другая очередь — одна, которую сетевой объект использует для завершения асинхронного ввода-вывода, а другая — просто для запоминания того, что конечный автомат получил указание сделать от клиента. - person Steve Jessop; 12.08.2009
comment
@onebyone Не могли бы вы уточнить, какова цель 3-й очереди? - person Piotr Dobrogost; 12.08.2009
comment
Я предлагаю две очереди — ту, что у вас уже есть, которую система асинхронного ввода-вывода использует для доставки событий, связанных с сетевыми коммуникациями, плюс вторую очередь, вероятно, полностью управляемую конечным автоматом. Если конечный автомат получает указание выполнить переход, который еще невозможен, он добавляет элемент в эту очередь. Всякий раз, когда он завершает переход, он проверяет очередь, чтобы увидеть, есть ли еще работа, которую он может сделать. - person Steve Jessop; 12.08.2009
comment
@onebyone События асинхронного ввода-вывода доставляются в мой код, а не напрямую в очередь или машину. Мне приходится пересылать их вручную. Кроме того, собственный цикл событий машины не ставит в очередь события, которые он получает. Так что может быть случай, когда машина будет занята в момент прихода сетевого события и пропустит это событие. Я не хочу, чтобы это произошло. Сейчас у меня есть только одна очередь, и я полностью ею управляю. Я также сам управляю событиями ввода-вывода. См. мой ответ на этот вопрос, где я подробно описал свой текущий дизайн. - person Piotr Dobrogost; 12.08.2009

Задавая этот вопрос, у меня уже был рабочий дизайн, о котором я не хотел писать, чтобы не искажать ответы в любом направлении :) Я собираюсь описать в этом псевдоответе, какой у меня дизайн.

Помимо конечного автомата у меня есть очередь событий. Вместо того, чтобы отправлять события непосредственно на машину, я помещаю их в очередь. Однако существует проблема с сетевыми событиями, которые являются асинхронными и происходят в любой момент. Если очередь не пуста и приходит сетевое событие, я не могу поместить его в очередь, потому что машина застрянет в ожидании, прежде чем обработать события, уже находящиеся в очереди. И машина будет ждать вечно, потому что это сетевое событие ожидает позади всех событий, помещенных в очередь ранее.
Чтобы решить эту проблему, у меня есть два типа сообщений; обычные и приоритетные. Обычные — это отправленные мной, а приоритетные — все сетевые. Когда я получаю сетевое событие, я не помещаю его в очередь, а вместо этого отправляю прямо на машину. Таким образом, он может завершить свою текущую задачу и перейти к следующему состоянию, прежде чем извлекать следующее событие из очереди событий.
Он работает таким образом только потому, что мои события и сетевые события чередуются ровно 1:1. Из-за этого, когда машина ожидает сетевого события, она ничем не занята (поэтому готова принять его и не пропустить) и наоборот - когда машина ждет мою задачу, она ждет только мою задачу, а не другую сеть одна.

Я задал этот вопрос в надежде на более простой дизайн, чем тот, что у меня есть сейчас.

person Piotr Dobrogost    schedule 12.08.2009

Строго говоря, нельзя. Поскольку у вас есть только состояние «Подключение», вы не знаете, нужен ли вам верхний вход после этого. Вам нужно будет ввести состояние «ConnectingWithIntentToLogin», чтобы представить результат события «Подключиться, затем войти» из состояния «Пуск».

Естественно, будет много перекрытий между состояниями «Подключение» и «ConnectingWithIntentToLogin». Этого проще всего добиться с помощью архитектуры конечного автомата, которая поддерживает иерархию состояний.

--- редактировать ---

Читая ваши более поздние реакции, теперь ясно, в чем ваша настоящая проблема.

Очевидно, вам нужно дополнительное состояние, вне зависимости от того, встроено ли оно в FSM или вне его в отдельной очереди. Давайте следовать модели, которую вы предпочитаете, с дополнительными событиями в очереди. Суть здесь в том, что вам интересно, как «перемежать» эти события в очереди с событиями в реальном времени. Вы этого не сделаете — события из очереди активно извлекаются при входе в определенные состояния. В вашем случае это будут состояния «* ed», такие как «Подключено». Только когда очередь пуста, вы останетесь в состоянии «Подключено».

person MSalters    schedule 12.08.2009
comment
Это хорошая информация, но это не ответ на мой вопрос. Прямо сейчас я всегда вхожу в систему после подключения, поэтому у меня только состояние Connecting. Предположим, я добавлю состояние ConnectingWithIntentToLogin. Что теперь? Вопрос заключается в том, как принудительно чередовать события. - person Piotr Dobrogost; 12.08.2009
comment
Просто: если в состоянии ConnectingWithIntentToLogin вы получаете событие Connect, вы не переходите в состояние Connected. Вместо этого вы выполняете команду «Войти» и переходите в состояние «Вход в систему». Что там чередовать? - person MSalters; 12.08.2009
comment
@MSalters У меня есть только одно сетевое событие — LoadFinished. Как вы можете узнать, будучи внутри обработчиком этого события, какое событие вы должны генерировать? В настоящее время, когда я получаю это сетевое событие, я всегда отправляю одно и то же событие Loaded на машину, и она переходит, скажем, в состояние Connected, когда уже находится в состоянии Connecting, или переходит, скажем, в состояние LoggedIn, если уже находится в состоянии LoggingIn. - person Piotr Dobrogost; 12.08.2009
comment
О, эту деталь я упустил. Но тогда это совсем неважно. Это просто означает, что LoadFinished должен обрабатываться всеми состояниями, в которых он может быть получен. Эти состояния не будут состояниями, в которых вы извлекаете событие из очереди. - person MSalters; 12.08.2009
comment
@MSalters Из ваших комментариев я вижу, что вы хорошо разбираетесь в теме, поэтому я хотел бы услышать от вас больше комментариев. Ситуация, которую вы описываете, такова, как она есть сейчас. Однако у меня есть стойкое ощущение, что в текущем дизайне логика моего приложения смешивается с логикой навигации по веб-странице. Это две разные вещи, и я хотел бы найти дизайн, который бы их разделял. У меня также есть трудная проблема решить, где данные должны быть помещены и переданы - в событиях, в переходах, в слотах или, может быть, в более чем одном месте? Это интересная проблема, и, возможно, мне следует задать ее в новом вопросе. Идеи? - person Piotr Dobrogost; 12.08.2009
comment
@MSalters Вы предложили добавить новые состояния в мой конечный автомат. В то же время у меня есть прямо противоположный совет здесь qtcentre.org/forum/ Не могли бы вы взглянуть и сказать мне, что вы об этом думаете? - person Piotr Dobrogost; 12.08.2009

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

person Michael Foukarakis    schedule 12.08.2009
comment
Меня не волнуют сетевые ответы в тот момент, когда я отправляю серию событий, но каждое из этих событий имеет значение, поскольку ему нужна информация из предыдущих. Поскольку эти события отправляются мной, вы можете сказать, что я забочусь о сетевых ответах а) транзитивно и б) в будущем :) - person Piotr Dobrogost; 12.08.2009

Как насчет перемещения конечного автомата в другой поток, т.е. е. QThread. Я бы внедрил входную очередь в конечный автомат, чтобы я мог отправлять запросы без блокировки, и выходную очередь для чтения результатов запросов. Вы даже можете вызвать выделенную функцию в своем основном потоке через connect(...), если будет получен результат запроса, в этом отношении Qt является потокобезопасным.

Таким образом, ваша конечная машина может блокироваться столько времени, сколько потребуется, не блокируя вашу основную программу.

person Gunther Piez    schedule 12.08.2009

Похоже, вы просто хотите составить список блокирующих операций ввода-вывода в фоновом режиме.

Итак, выполните поток:

while( !commands.empty() )
{
  command = command.pop_back();
  switch( command )
  {
  Connect: 
    DoBlockingConnect();
    break;
  ...
  }
}
NotifySenderDone();
person jyoung    schedule 12.08.2009
comment
1. Я не могу блокировать ввод-вывод в той среде, которую использую. 2. Речь не идет о выполнении серии блокирующих операций ввода-вывода, потому что после каждой операции ввода-вывода мне приходится передавать управление конечному автомату, который решает, что делать дальше. - person Piotr Dobrogost; 12.08.2009
comment
1) Обратите внимание, что в сетевом модуле QT операторы waitfor... превращают неблокирующий ввод-вывод в блокирующий ввод-вывод. Например: socket.connectToHost(serverName, serverPort); socket.waitForConnected(); 2) Поймите, что ваш список приведенных команд имеет простое соответствие 1-1 блокирующим операциям ввода-вывода. Похоже, вы стремились сделать конечный автомат ввода-вывода простым и тупым, а клиент для конечного автомата ввода-вывода решил (через список команд) последовательность ввода-вывода. - person jyoung; 13.08.2009