Почти все разработчики программного обеспечения рано или поздно подходят к этапу, когда необходимо применить технологию распределения задач по отдельным потокам. Сейчас трудно представить разработку без применения того или иного фреймворка (framework).
Множество из них содержат необходимые инструменты для создания многопоточных приложений. Не исключение и Qt Framework.

Поговорим о методах Qt многопоточной разработки подробнее.

Сообщество разработчиков ПО на Qt Framework огромно. Люди охотно делятся навыками и приёмами создания многопоточных приложений. Существует множество замечательных книг и статей по вопросам изящного и не очень решения задачи работы с несколькими потоками.
Казалось бы, всё уже решено. Что ещё можно добавить?
Попробую описать работу потока на основе функционирования конечного автомата. Признаюсь, не находил материалы с подобным решением в сети.
Если статья поможет вам с идеей, что, по моему мнению, гораздо ценнее написанного кода, буду очень рад.

Выражаю отдельную благодарность А. Н. Боровскому за книгу «Qt4.7+.Практическое программирование на C++».
Рекомендую к обязательному прочтению!

Мне так удобно.


В своих кодах я определяю несколько макросов, которые могут показаться избыточными:
#define IS_ZERRO(VALUE)   (0 == (VALUE))
#define MORE_ZERRO(VALUE) (0 < (VALUE))
#define LESS_ZERRO(VALUE) (0 > (VALUE))
#define NOT_ZERRO(VALUE)  (!IS_ZERRO(VALUE))

#define IS_NULL(PTR)      (Q_NULLPTR == (PTR))
#define NOT_NULL(PTR)     (!IS_NULL(PTR))

#define BETWEEN(VALUE,LOW,HIGH) ( ((VALUE) > (LOW)) && ((VALUE) < (HIGH)) )
#define BETWEEN_L(VALUE,LOW,HIGH) (((VALUE) >= (LOW)) && ((VALUE) < (HIGH)))
#define BETWEEN_H(VALUE,LOW,HIGH) (((VALUE) > (LOW)) && ((VALUE) <= (HIGH)))
#define BETWEEN_ALL(VALUE,LOW,HIGH) (((VALUE) >= (LOW)) && ((VALUE) <= (HIGH)))

#define EQUAL(VALUE,EXPR) ((VALUE) == (EXPR))
#define NOT_EQUAL(VALUE,EXPR) (!EQUAL(VALUE,EXPR))



Не буду спорить о целесообразности их применения. Мне кажется, мнемоника не перегружает код и содержит, скажем, «хороший тон» для проверки указателей.
Просто, мне так удобно.

Начнём.

Шаг 1. Немного о потоках «на пальцах».


Если вы правильно поставили ударение в последнем слове, продолжим. :)
Как известно, модель потоков Qt состоит из двух частей:
  • Главный поток приложения;
  • Воспомогательные потоки.

Реализуя объекты-наследники класса QThread (рус.), мы имеем дело с созданием очередных воспомогательных потоков.
Каждые объект класса QThread содержит собственный цикл обработки событий. Цикл запускается защищённым методом QThread::exec(). Запуск потока на выполнение производится вызовом метода QThread::start(). Метод старт запускает на выполнение защищённый метод QThread::run(). В свою очередь, метод run() запускает exec() и, соответственно, собственный обработчик событий для объекта класса QThread.
Метод run() выполняется в адресном пространстве отдельного (назовём его, рабочим) потока. Адресное пространство рабочего потока отличается от адресного пространства объекта класса QThread.

Как реализуется рабочий поток?
Для этого, например, в UNIX-подобных системах используется библиотека pthread. В сети достаточно много материалов о ней.
Метод QThread::start() создаёт отдельный рабочий поток вызовом pthread_create(), передавая ему в качестве функции потока метод QThreadPrivate::start (), а в качестве последнего параметра arg указатель на объект-себя.

Интересен код внутри метода
QThreadPrivate::start ()
{
    . . .
    QThreadData *data = QThreadData::get2(thr);
    . . .
}


Отследив цепочку связей, можно понять, что все объекты, перемещаемые методом QObject::moveToThread(QObject*) будут помещены именно в этот пул данных класса QThreadData адресного пространства рабочего потока.
Немало дискуссий в сообществе разработчиков Qt ведутся на тему, что же лучше: перемещать объекты методом QObject::moveToThread () или перегружать защищённый метод QThread::run()?
Переопределив метод QThread::run(), знайте, что все объекты, созданные в нем, будут недоступны из вне, т.к. созданы в стеке метода run() (хотя и это можно обойти).
Перемещаемые же объекты хранятся в пуле данных и тянут за собой все свои QMetaObject::Connection-ы.

Помните об этом, и всё будет в порядке.

Упрощённую схему всего вышесказанного можно представить рисунком:



Немного о целесообразности использования QThread.


Помимо QThread, Qt предоставляет разработчику набор классов более «высокого» уровня.
Они расположены в пространстве имен QtConcurrent. Классы данного интерфейса полностью берут на себя управление потоком, оптимизируют работу и распределяют нагрузку между потоками. Вы можете набросать в них методов и не заботиться о корректном завершении и очистке памяти. Но есть издержки. Например, класс QRunnable не является потомком QObject, значит, не может отправлять или получать сигналы и не может обрабатывать события.
Думаю, они хорошо подходят для простых методов, например, каких либо математических вычислений, где существует определённый заранее набор входных данных и строгий алгоритм обработки.
Часто приходится создавать постоянные циклы ожидания чего-либо или постоянного сканирования и проверки различных условий. Разработчик помещает их в отдельные потоки для того, чтобы основной цикл обработки события или GUI приложения не «тормозили». Например, используя функции библиотеки libUSB, можно постоянно обновлять список подключённых устройств. В данном случае может оказаться несколько возможных точек выхода из потока как реакция на внешние события, сигналы или возникновения исключительных ситуаций. Главное — мы должны управлять алгоритмом, заключённым в цикл рабочего потока. Для этого, думаю, применение объекта класса производного от класса QThread наиболее оправдано.

Замечание.


Довольно часто в сети встречается некий «финт ушами» по перемещению объекта потока в рабочий поток, а именно
MyThread:: MyThread () : QThread()
{    moveToThread(this);  }

Не делайте этого! Хотя бы потому, что нарушается основная концепция класса QThread как надстройки по управлению потоками. Это похоже на то, что вы пришли на рыбалку, забросили удочку, да и саму удочку тоже забросили в реку…

Создавая объекты потока, вы, наверное, заметили, что значение указателя на объект, перемещённый методом QObject::moveToThread() в адресное пространство рабочего потока, корректно отрабатывает ссылку вне потока. Мы спокойно можем использовать объект по, так сказать, перемещённому указателю для связей QMetaObject::Connection.
Казалось бы, переместили в другое адресное пространство, а, получается так, что никакого перемещения и не было: адрес объекта по значению указателя не изменился.
Рассмотрим одно из отличий процесса и потока. Каждому процессу ОС выделяет собственный пул памяти, но каждый новый поток использует общую память с родительским потоком и процессом, его создавшим. Значит, всё в норме – адрес перемещаемого объекта из потока в поток не меняется относительно основного процесса.
Можно сказать, указатель на объект относительно адресных пространств потоков, чем-то сродни «умному» указателю типа std::auto_ptr, закрепляющему объект за владельцем.
Приведу лирическую аналогию.
//На планете Земля (основной процесс main()) некая древняя цивилизация основала город Кафа 
// с вполне определёнными географическими координатами.
City/*::QObject*/* city = City();
city->setObjectName(“Кафа”);
std::out << qPrintable(city) << std::endl;
. . .
//Через века цивилизация утратила своё влияние на город, и город стал принадлежать Крымскому ханству,
QThread* Girey = new QThread(); 
city->setParent(Q_NULLPTR); 
city->moveToThread(Girey);

//  но географические координаты города не изменились.
std::out << qPrintable(city) << std::endl;
. . .
// Что бы ни происходило 
city->setParent(Q_NULLPTR); 
city->moveToThread(RussionImperia); 
. . . 
city->setParent(Q_NULLPTR); 
city->moveToThread(USSR);
. . .
city->setObjectName(“Феодосия”);

// и т.д. 

Географические координата города (city) относительно Земли (main()) не меняются, но инфраструктура (*city) изменчива.

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

Шаг 2. Корректное завершение потока.


Как известно, потоки могут обмениваться информацией, использовать общие данные, анализировать состояние этих данных и отправлять друг другу различные уведомления.
Один поток косвенно или напрямую может попросить другой приостановить на время работу или, вообще, прекратить своё существование.
Не буду описывать все возможные методы и варианты корректного разделения доступа к данным: материала по данной тематике более чем достаточно. Просто, представим ситуацию, что мы не знаем заранее, когда произойдёт выход из потока, и по какому условию это случиться. Конечно, точки выхода из потока должны срабатывать по завершению некого конечного участка работы потока.
Может возникнуть ситуация, когда сам алгоритм вызовет исключение. Например, обращение к указателю с несуществующим значением. Часто это приводит к полному зависанию потока. Даже метод QThread::terminate() не сможет удалить его из пула выполнения. Так появляются зомби-потоки. В случае с процессами ОС сама попытается уничтожить процесс, но с потоками этот номер не пройдёт, т.к. за их жизнь отвечает основной поток, а не ядро ОС.
Одним словом, мы будем считать, что заранее не знаем полное количество и места точек выхода из потока и код завершения.
Здесь нужен некий помощник – обверка “helper” вокруг объекта потока. Подобные трюки встречаются часто в Qt.

Приведу пример кода и коротко поясню:
Определён класс потока FWThread:
class FWThread : public QThread 
{
}

Определён класс помощника
class FWThread_helper
{
  QPointer<FWThread> m_ext_thread;
Public:
    FWThread_helper(FWThread* aThread)
      : m_ext_thread(aThread)
    {
      Q_ASSERT(NOT_NULL(m_ext_thread));
    }

    ~FWThread_helper()
    {
      if(!m_ext_thread.isNull())
        m_ext_thread->deleteLater();
    }
}

Определим деструктор класса FWThread
FWThread::~FWThread()
{
  quit ();
  if(!wait (FW_THREAD_WAIT_TIMEOUT))
  {
    // ну, очень нехороший случай...
    terminate ();
    if(!wait(FW_THREAD_WAIT_TIMEOUT))
      exit(EXIT_FAILURE);
  }
}

Переопределён метод QThread::run ()
(Спасибо ArtemE за замеченную помарку)
void
FWThread::run()
{
  m_helper = new FWThread_helper(this);

//алгоритм работы потока

  m_helper.clear ();
}

Что произойдёт, например, при возникновении исключительной ситуации в теле метода run()? Что бы ни случилось, указатель v_helper всегда вызовет собственный деструктор либо по m_helper.clear (), либо по очистке стека вызовов метода run() при возникновении исключения.
Деструктор уничтожит объект потока FWThread. По завершению последнего произойдёт попытка выхода из потока или ваш собственный некий алгоритм закрытия потока. Например, все объекты переданные потоку, можно разрушить методом QObject::deleteLater().

Обратите внимание на строку
Q_ASSERT(NOT_NULL(m_ext_thread));

в теле конструктора класса FWThread_helper. Класс создан специально для непустого указателя на поток. Данный код информирует разработчика об ошибке уаправления потоком на этапе отладки.

Попробуйте определить
void
FWThread::run()
{
  FWThread_helper* v_helper = new FWThread_helper(this);
  QException e;
  
  QT_TRY 
  {
    e.raise();
  } QT_CATCH(QException& e) {
    qDebug() << qPrintable(e.what ());
  }
  m_helper.clear ();
}

Надеюсь, здесь всё понятно.

Шаг 3. Применение иерархического конечного автомата.


Одна из полезнейших, на мой взгляд, реализаций в Qt Framework представлена разделом иерархического конечного автомата (далее КА). КА реализован и в библиотеке boost, но речь пойдёт о Qt.

Ознакомиться с теорией Qt КА можно на странице документации Qt или сайта crossplatform.ru
Каким же образом и, главное, для чего связывать КА и поток?
Немного забегая вперёд, приведу пример используемого мною кода перегрузки метода QThread::run():
void
FWThread::run()
{
  FWThread_helper* v_helper = new FWThread_helper(this);
  m_helper = v_helper;

  InternalRun ();

  if(IsValid ())
  {
    BeforeMachineStart ();

    /// Запуск детерминированного Конечного Автомата @sa Machine ().
    Machine ()->start ();

    /// Запуск циклa обработки событий потока. => Начало работы КА.
    BaseClass::exec ();
  }
  else
  {
    if(!IsError ()) SetError ();
    exit (FW_ERR_THREAD_INCOMPLETE);
  }

  m_helper.clear ();
}

Во всех классах, образованных от класса FWThread(::QThread), я переопределяю лишь единственный собственноручно добавленный метод FWThread::BeforeMachineStart(). Никакой метод непосредственной работы с потоком больше не перегружается!
Каждый метод BeforeMachineStart() нового класса наследника FWThreadдобавляет состояния КА и переходы между ними. Таким образом, происходит локализация изменения алгоритма метода QThread::run() лишь в одном файле реализации класса, а сам поток может обслуживать различные модели поведения конечного автомата. Причём, при остановке потока можно запретить QObject::deleteLater() для объекта класса FWThread, динамически изменить поведение КА и запустить тот же самый экземпляр потока методом FWThread::start() на выполнение заново с совершенно другой моделью поведения КА!

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

В реализации Qt конечный автомат представлен классом QStateMachine (рус.), состояния описаны классами QState (рус.), QHistoryState (рус.), QFinalState (рус.). Переходы представлены классами QEventTransition (рус.) и QSignalTransition (рус.).
Все перечисленные классы основаны на множестве абстрактных классов. Так QState расширяет абстрактный класс QAbstractState (рус.). Класс КА QStateMachine – производная от QState. Классы переходов ведут начало от класса QAbstractTransition (рус.).
Документация по Qt содержит весьма подробное описание КА, классов состояний и переходов, достаточное множество простых примеров и трюков при проектировании и реализации КА.
Существует отличный и полный перевод описания КА Qt на русском языке
Прочтите. Это, просто, интересно.

Из примеров и рекомендаций документации Qt для работы потока на основе КА я выбрал схему, аналогичную рисунку примера документации.


Переопределяя классы автомата, состояний и переходов, я на некоторое время отказался от использования класса QSignalTransition. Любой сигнал можно продублировать как посыл события обработчику события потока.
Представьте картину, когда у вас объект приёмника событий спрятан как атрибут некого класса. Объект последнего так же является атрибутом какого-либо класса и т.д. Чтобы оттранслировать сигнал, разработчику придётся не забыть о переназначении связей метода QObject::connect() вглубь объекта, пока сигнал не достигнет цели. И наоборот, ретранслировать сигналы глубоко вложенных объектов как атрибутов класса на верхний уровень.
Куда проще определить собственный формат сообщений на основе класса QEvent c некими дополнительными атрибутами и использовать метод QStateMachine::postEvent() или QCoreApplication::postEvent().

Дополнительно, я использую собственную модель классов, основнных на классе QObject.
Так, базовый класс у меня звучит как class FWIterm: public QObject.
Объявляя новые классы, я наследую их только от класса FWItem. Это позволяет ввести некие флаги и признаки, общие для всех. К тому же, наследники FWItem могут дополнять и расширять наборы флагов, множеств и прочих атрибутов типов класса FWItem.
Самое главное то, что базовый класс FWItem содержит методы назначения, проверки и ссылки на конечный автомат:

class FW_LIBRARY FWItem : public QObject
{
  private:
    QPointer<FWStateMachine> m_machine;

  protected:
    /**
     * @brief Метод MachineExists проверяет наличие в объекте класса конечного
     * автомата класса @sa FWStateMachine.
     * @return Булево значение:
     * @value TRUE Автомат присутствует в объекте класса;
     * @value FALSE Конечный автомат не используется.
     */
    virtual bool
    MachineExists () const { return !m_machine.isNull (); }

    /**
     * @brief Метод Machine возвращает указатель на встроенный детерминированный
     * конечный автомат (КА) типа @sa FWStateMachine.
     * @return Указатель на встроенный КА.
     *
     * Класс @sa FWStateMachine является наследником @sa QStateMachine.
     *
     * Метод предназначен для перегрузки при использовании в классах, производных
     * от @sa FWItem, конечного автомата, созданного на основе классас @sa FWStateMachine.
     */
    virtual FWStateMachine*
    Machine () const;
  public:
    /**
     * @brief Метод SetMachine назначает объекту класса конечный автомат.
     * @param aMachine Указатель на объект КА.
     */
    void
    SetMachine (FWStateMachine* aMachine);
}


Таким образом, при наличии КА для объекта наследника FWItem, можно управлять посылом сообщений для состояний и переходов напрямую:
if(item-> MachineExists())
  Machine()->PostEvent(FWEvent::OEI_Open);

Где FWEvent::OEI_Open некий, определённый идентификатор сообщения класса FWEvent.

Диаграмма состояний и переходов для автомата управления работой потока принимает вид,

где T(сигнал типа FWEvent::Event::Type) – переход из одного состояния в другое.

Для простоты понимания переходов между состояниями в класс FWStateMachine добавлен набор виртуальных методов FWStateMachine:: AddBranch(<событие или конструктор события><исходное состояние><состояние назначения> == Q_NULLPTR);
Для многих решений задач на основе КА Qt данной схемы и количества состояний вполне достаточно. Поэтому я внедрил её в тело реализации класса FWMachineState, создав защищённый (protected) виртуальный метод Initialisation ().
Рассмотрим его:

void
FWStateMachine::Initialisation()
{
  bool v_active = IsActive ();
  if(v_active)
    stop ();

  /// @name Определение группы состояний КА
  /// @{
  if(!StateGroupExists ())
    m_state_group.reset (new FWState(this));

  FWState* sIdle = new FWState(StateGroup ());
  StateGroup ()->setInitialState (sIdle);
  /// @}

  /// @name Назначение правил работы исторического состояния КА
  /// @{

  if(!StateHistoryExists ())
    // переход с историческим состоянием, в качестве цели, закончится в наиболее
    // глубоко вложенном состоянии-потомке @sa m_state_group (@sa QHistoryState::DeepHistory), 
    // в котором родительское
    // находилось в последний раз, когда из него вышли.
    m_state_history.reset (new QHistoryState(QHistoryState::DeepHistory,StateGroup ()));

  /// @}

  /// @name Определение состояниz завершения работы КА
  /// @{

  if(!StateFinalExists ())
    m_state_final.reset (new QFinalState(this));

  /// @}

  /// @name Определение ошибочного состояния КА
  /// @{

  if(!StateErrorExists ())
    m_state_error.reset (new FWState(this));

  /// @}

  /// @name Назначение переходов для основного состояния КА
  /// @{

  AddBranch(FWEvent::ET_INTERNAL,FWInternalEvent::OEI_Stop,
            StateGroup (), StateFinal ());
  AddBranch(FWEvent::ET_INTERNAL,FWInternalEvent::OEI_Error,
            StateGroup (), StateError ());

  /// @}

  /// @name Назначение переходов для состояния приостановки работы
  /// @{

  FWState* sPause = new FWState(this);
  AddBranch(FWEvent::ET_INTERNAL,FWInternalEvent::OEI_Pause,
            StateGroup (), sPause);
  AddBranch(FWEvent::ET_INTERNAL,FWInternalEvent::OEI_Resume,
            sPause, StateHistory ());

  /// @}

  /// @name Назначение переходов для состояния ошибки
  /// @{
  AddBranch(StateError (), StateFinal ());
  /// @}

  /// @name Назначение обработчиков переходов между состояниями КА
  /// @{

  connect( sPause,&FWState::        entered,
           this,  &FWStateMachine:: slot_OnPaused,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  connect( sPause,&FWState::        exited,
           this,  &FWStateMachine:: slot_OnResume,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  connect( StateFinal (),&QFinalState::    entered,
           this,         &FWStateMachine:: slot_OnStop,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  connect( StateError (),&FWErrorState::   entered,
           this,         &FWStateMachine:: slot_OnError,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  connect( StateHistory(),&QHistoryState::defaultStateChanged,
           this,          &FWStateMachine::slot_OnDefaultHistoryChanged,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  /// @}

  if(NOT_EQUAL(initialState (),StateGroup ()))
    setInitialState (StateGroup ());

  StateGroup ()->   setObjectName("State GROUP");
  StateHistory ()-> setObjectName("State HISTORY");
  StateError()->    setObjectName("State ERROR");
  StateFinal()->    setObjectName("State FINAL");
  sPause->          setObjectName("State PAUSE");
  sIdle->           setObjectName("State IDLE");

  if(v_active) start ();
}



В наследниках класса FWStateMachine
можно перегрузить слоты

  . . .
  protected Q_SLOTS:

    virtual void
    slot_OnLoging ()
    { qDebug() << qPrintable(Q_FUNC_INFO); }

    virtual void
    slot_OnError ()
    { qDebug() << qPrintable(Q_FUNC_INFO); }

    virtual void
    slot_OnPaused ()
    {
      qDebug() << qPrintable(Q_FUNC_INFO);
      if(thread ())
        thread ()->yieldCurrentThread ();
      emit sign_Paused();
    }

    virtual void
    slot_OnResume ()
    {
      qDebug() << qPrintable(Q_FUNC_INFO);
      emit sign_Resumed();
    }

    virtual void
    slot_OnStop ()
    {
      qDebug() << qPrintable(Q_FUNC_INFO);
    }

    virtual void
    slot_OnDefaultHistoryChanged()
    {
      if(StateHistoryExists ())
        if(NOT_NULL(StateHistory()->defaultState()))
          qDebug() << "-H-" << StateHistory()->defaultState()->objectName();
    }
};



Вернёмся к классу FWThread и посмотрим на метод Reconnect(), вызываемый в теле метода run()
FWThread::Reconnect()

void
FWThread::Reconnect()
{
  if(IsAutoDeleted ())
    // Уничтожение экземпляра потока после завершения работы
    connect( this, &QThread::finished,
             this, &QThread::deleteLater,
             Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection));

  if(MachineExists ())
  {
    // Завершение цикла обработки событий потока при достижении конечным
    // автоматом состояния завершения работы
    connect( Machine (),&FWStateMachine:: finished,
             this,      &QThread::        quit,
             Qt::ConnectionType(Qt::AutoConnection | Qt::UniqueConnection));

    // Завершение работы конечного автомата после согнала потока о
    // завершении работы.
    connect( this,      &QThread::        finished,
             Machine (),&FWStateMachine:: stop,
             Qt::ConnectionType(Qt::AutoConnection | Qt::UniqueConnection));

    /// @name Немедленная ретрансляция сигналов конечного автомата из @sa FWThread
    /// @{
    connect (Machine (),&FWStateMachine:: started,
             this,      &FWThread::       sign_MachineStarted,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));

    connect (Machine (),&FWStateMachine:: stopped,
             this,      &FWThread::       sign_MachineStopped,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));

    connect (Machine (),&FWStateMachine:: sign_Paused,
             this,      &FWThread::       sign_MachinePaused,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));

    connect (Machine (),&FWStateMachine:: sign_Resumed,
             this,      &FWThread::       sign_MachineResumed,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
    /// @}

    // обязать объект потока выполнить некоторые действия немедленно после
    // запуска конечного автомата
    connect (Machine (),&FWStateMachine:: started,
             this,      &FWThread::       slot_AfterMachineStart,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  }
}


В коде я стараюсь подробно описывать все действия. Привычка помогает минимизировать текст статьи.

Осталось немного: добавить в класс-помощник FWThread_helper для класса FWThread пару полезных деталей и методов. Вы найдёте их, просмотрев прилагающийся листинг.
FWThread.h

///
/// \language Russian
///

#ifndef FW_THREAD_H
#define FW_THREAD_H

#include <FWTypes>

FW_BEGIN_NAMESPACE

//------------------------------------------------------------------------------
/**
 * @brief Внешняя переменная FW_THREAD_WAIT_TIMEOUT содержит значение
 * задержки ожидания для метода @sa QThread::wait.
 *
 * @see QThread
 */
extern unsigned long FW_THREAD_WAIT_TIMEOUT;

//------------------------------------------------------------------------------
class FWThread_helper;
class FWStateMachine;

//------------------------------------------------------------------------------
class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWThread : public QThread
{
    friend class FWThread_helper;

    Q_OBJECT

    Q_CLASSINFO("brief",          "Framework Thread Class with QStateMashine")
    Q_CLASSINFO("created",        "03-JUN-2015")
    Q_CLASSINFO("modified",       "23-JUN-2015")
    //
    Q_CLASSINFO("project",        "Common Qt-based Framework")
    //
    Q_CLASSINFO("info_ru",        "http://doc.crossplatform.ru/qt/4.7.x/qthread.html#")

//    Q_DISABLE_COPY(FWThread)

    /// @name Локальные типы класса.
    /// @{
    /// приведение типа базового класса для независимости реализации
    typedef QThread BaseClass;
    /// @}

  public:
    enum FWThreadFlag
    {
      THF_Empty       = 0x00,
      THF_Ready       = 0x01,
      THF_Working     = 0x02,
      THF_Paused      = 0x04,
      THF_AutoDelete  = 0x08,
      THF_Error       = 0x80,
    };
    Q_DECLARE_FLAGS(FWThreadFlags, FWThreadFlag)

  private:

    QPointer<FWThread_helper> m_helper;

    ///
    /// @brief Атрибут m_flags представляет флаги состояния экземпляра потока.
    /// @see FWThreadFlags.
    ///
    QAtomicInt   m_flags;

    /// @name Закрытые (служебные) методы работы с флагами класса
    /// @{

    void
    SetEmpty ()
    {
      m_flags.fetchAndStoreOrdered (THF_Empty);
    }

    void
    SetReady ()
    {
      FWThreadFlags v_flags = Flags () | THF_Ready;
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void UnsetReady ()
    {
      FWThreadFlags v_flags = Flags () & (~THF_Ready);
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    SetWorking ()
    {
      FWThreadFlags v_flags = Flags () | THF_Working;
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    SetStop ()
    {
      FWThreadFlags v_flags = Flags () & (~THF_Working);
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    SetPause ()
    {
      FWThreadFlags v_flags = Flags () | THF_Paused;
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    SetResume ()
    {
      FWThreadFlags v_flags = Flags () & (~THF_Paused);
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    SetAutoDelete ()
    {
      FWThreadFlags v_flags = Flags () | THF_AutoDelete;
      m_flags.fetchAndStoreOrdered (v_flags);
      Reconnect();
    }

    void
    UnsetAutoDelete ()
    {
      FWThreadFlags v_flags = Flags () & (~THF_AutoDelete);
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    SetError ()
    {
      FWThreadFlags v_flags = Flags () | THF_Error;
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    UnsetError ()
    {
      FWThreadFlags v_flags = Flags () & (~THF_Error);
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    /// @}

    /**
     * @brief Метод InternalRun производит подключение правил взаимодействий
     * экземпляра класса и конечного автомата с установкой признака готовности.
     */
    void
    InternalRun();

  public:

    /**
     * @static
     * @brief Метод SetThreaWaitTimeout назначает новое значение
     * задержки ожидания для метода @sa QThread::wait.
     * @param aValue новое значение задержки ожидания для метода @sa QThread::wait.
     *
     * @see QThread
     */
    static void
    SetThreaWaitTimeout( unsigned long aValue)
    {
      if(NOT_EQUAL(FW_THREAD_WAIT_TIMEOUT,aValue))
        FW_THREAD_WAIT_TIMEOUT = aValue;
    }

    /**
     * @brief Конструктор объекта @sa FWThread
     * @param aAutoDelete Признак автоматического разрушения объекта класса по
     * окончании работы потока.
     * @param aParent указатель на объект-родитель класса.
     *
     * @note Если указатель @sa aParent на объект-родитель класса не пуст,
     * значение параметра @sa aAutoDelete игнорируется, и поток не разрушается
     * автоматически после завершения работы потока выполнения.
     */
    explicit
    FWThread(const bool aAutoDelete = true, QObject* aParent = Q_NULLPTR);

    ~FWThread();

    /**
     * @brief Метод AsBaseClass представляеь указатель на экземпляр (оюъект)
     * класса @sa FWThread как указатель на базовый класс @sa QThread.
     * @return указатель типа @sa QThread на экземпляр дапнного класса.
     */
    BaseClass*
    AsBaseClass () { return qobject_cast<BaseClass*>(this); }

    ///
    /// @brief Метод setParent перегружает базовый метод @sa QObject::setParent.
    /// @param aParent Указатель на объект "родителя"
    /// Метод введён в класс с целью предотвращения вызова автоматического
    /// разрушения объекта класса в случае начальной установки флага @sa aAutoDelete
    /// в значение TRUE.
    /// @see FWThread::FWThread
    ///
    virtual void
    setParent(QObject* aParent);

    /**
     * @brief Метод Flags позволяет получить список флагов экземпляра FWThread.
     * @return Значение флагов потока типа @sa FWThread::FWThreadFlags
     */
    inline FWThreadFlags
    Flags () const { return FWThreadFlags(m_flags.loadAcquire ()); }

    /**
     * @brief Метод Machine возвращает указатель на КА, упроавляющий работой
     * потока.
     * @return указатель на КА текущего экземпляра класса.
     *
     * @warning Конечный автомат выполняется в адресном пространстве рабочего
     * потока, не совпадающем с адресным пространством объекта текущего
     * класса.
     */
    FWStateMachine*
    Machine ();

    /**
     * @brief Метод MachineExists проверяет доступность указатела на КА.
     * @return Булево значение:
     * @value TRUE сообщает о наличии КА;
     * @value FALSE сообщает о том, что экземпляр КА не создан.
     */
    inline bool
    MachineExists () { return NOT_NULL(Machine ()); }

    /**
     * @brief Метод IsValid сообщает об соответствии экземпляра данного класса
     * условиям применения во внешних к объекту класса вычислениях.
     * @return Булево значение:
     * @value TRUE сообщает о выполнении условий валидности экземпляра класса;
     * @value FALSE сообщает о неготовности экземпляра класса к применению.
     */
    virtual bool
    IsValid ();

    /**
     * @brief Метод IsReady информирует о готовности объекта класса к запуску.
     * @return Булево значение:
     * @value TRUE сообщает о готовности объекта потка к запуску;
     * @value FALSE сообщает о неготовности объекта класса к запуску.
     */
    inline bool
    IsReady () const { return Flags ().testFlag (THF_Ready); }

    /**
     * @brief Метод IsAutoDeleted информирует об автоматическом уничтожении
     * экземпляра данного класса после завершения работы основного метода потока.
     * @return Булево значение:
     * @value TRUE сообщает, что объект данного класса будет разрушен автоматически;
     * @value FALSE сообщает, что объект данного класса не будет разрушен автоматически.
     *
     * Например,
     *  @<code>
     *  void foo ()
     *  {
     *    FWTread* v_thread = new FWTread(true);
     *    bool v_with_timeout = <условие>;
     *    ...
     *    thread.quit ();
     *
     *    if(v_with_timeout)
     *    {
     *      if(!thread.wait (FW_THREAD_WAIT_TIMEOUT) )
     *      {
     *      // например,
     *      //  terminate ();
     *      //  wait(1000);,
     *
     *      //  Хотя terminate() не рекомендуется, но наш класс его
     *      //  переопределил как quit() ;)
     *      ...
     *      }
     *    }
     *    else
     *      v_thread->Machine()->stop ();
     *      //Останов конечного автомата вызовет FWTread::quit, FWTread::finished
     *      //вызовет FWTread::deleteLater, произойдёт разрушение v_thread, и
     *      //вызовется деструктор класса @sa FWThread_helper атрибута FWTread::m_helper.
     *  }
     * @</code>
     */
    inline bool
    IsAutoDeleted () const { return Flags ().testFlag (THF_AutoDelete); }

    inline bool
    IsError () const { return Flags ().testFlag (THF_Error); }

    inline bool
    IsWorking () const { return Flags ().testFlag (THF_Working); }

    inline bool
    IsPaused () const { return Flags ().testFlag (THF_Paused); }

    /**
     * @brief Метод AttachObject перемещает объект в адресное пространство
     * потока выполнения.
     * @param aObject Указатель на объект.
     * @return Булево значение:
     * @value TRUE сообщает, что объект перемещён успешно;
     * @value FALSE сообщает, что перемещение объекта вызвало ошибки.
     */
    bool
    AttachObject (QObject* aObject);

    /// @name Перегруженные методы класса @sa QThread
    /// @{

    /**
     * @brief Переопределённый метод класса @sa QThread::terminate.
     * Перед выходом из потока выполнения останавливает КА, если тот активен.
     */
    void terminate();

    /**
     * @brief Переопределённый метод класса @sa QThread::quit.
     * Перед выходом из потока выполнения останавливает КА, если тот активен.
     */
    void quit();

    /// @}

  protected:

    /**
     * @brief Метод Reconnect определяет взаимодействие экземпляра конечного
     * автомата и потока.
     *
     * Посредством привязки сигналов потока и экземпляра КА к слотам потока и КА
     * определяется логика запуска и завершения работы КА. Также порядок
     * разрушения экземпляра КА и экземпляра потока.
     */
    void
    Reconnect ();

    /**
     * @brief Метод run перегружает базовый метод @sa QThread::run.
     * Данный метод инициализирует объект @sa m_helper и начальную настройку
     * класса методом @sa InternalRun
     *
     * До запуска метода цикла обработки событий методом @sa exec() данный метод
     * перенмещает экземпляр КА в адресное пространство потока.
     * Все зависимык от КА объекты, определённые как дочерние, так же автоматически
     * перемещаются в адресное пространство потока.
     * После этого происходит запуск цикла обработки событий методом @sa exec().
     *
     * Метод запускакт Конечный Автомат и цикл обработки событий потока на
     * выполнение.
     * Перед этим вызывается метод @sa BeforeMachineStart.
     *
     * @note Переопределите метод @sa BeforeMachineStart для добавления
     * некоторых состояний и переходов КА.
     *
     * @warning При переопределении не забывайте переместить необходимые объекты
     * в адресное пространство потока выполнения вызовом метода @sa AttachObject.
     *
     * @see QThreadPrivate::start, QThread::start, QThread::exec, QThread::exit.
     */
    void
    run() Q_DECL_OVERRIDE;

  public:

    /**
     * @brief Метод BeforeMachineStart позволяет выполнить некоторые особые
     * действия перед запуском КА и основного алгоритма потока на выполнение.
     *
     * Изначально, КА запрограммирован на работу основных состояний и переходов
     * между ними.
     * Перегрузка данного метода позволяет, скажем, перед запуском КА внести
     * дополнительные состояния КА и переходы между ними или перенаправить
     * существующие.
     *
     * @note Используйте перегрузку данного метода для определения собственных
     * состояний и переходов КА в дополнение к базовым. В классе @sa FWStateMachine
     * определено начальное состояние как группа для возможных пользовательских
     * состояний. Доступ к нему можно получить методом вызова последовательности
     * методов Machine ()->StateGroup (). В самой группе изначально определено
     * собственное начальное состояние, которое можно использовать для начала
     * переходов. Доступ к начальному состоянию группы можно получить вызовом
     * метода Machine ()->StateIdle ().
     *
     * Пример перегрузки метода @sa BeforeMachineStart в классе FWUsbScanner
     * определения подключения/отключения USB-устройств:
     * @<code>
     *
     * @<code>
     *
     * @see FWStateMachine, FWState
     * @see QStateMachine, QState, QAbstractTransition
     */
    virtual void
    BeforeMachineStart () {}

    /**
     * @brief Метод BeforeThreadDone позволят выполнить некоторые особые
     * действия перед завершением работы основного алгоритма потока.
     *
     * Необходимость в таком методе возникает потому, что у процедуры потока
     * есть только одна точка входа, но может быть много точек выхода.
     *
     * Метод @sa BeforeThreadDone() избавляет от необходимости дублировать код.
     *
     * Для внесения специфичных действий необходимо перегрузить данный метод.
     *
     * @note Важно помнить: тело метода будет выполняться в адреадресном
     * пространстве потока, но не в адресном пространстве экземпляра
     * данного класса @sa FWThread!
     *
     * @note Важно помнить: тело метода будет выполняться в адреадресном
     * пространстве потока, но не в адресном пространстве экземпляра
     * данного класса @sa FWThread!
     *
     * Данный метод будет вызван даже в случае возникновения исключительной
     * ситуации в методе @sa FWThread::run.
     */
    virtual void
    BeforeThreadDone() {}

    /**
     * @brief Метод AfterThreadDone позволят выполнить некоторые особые действия
     * после завершения работы основного алгоритма потока.
     *
     * Необходимость в таком методе возникает потому, что у процедуры потока
     * есть только одна точка входа, но может быть много точек выхода.
     *
     * Метод @sa AfterThreadDone, как и метод @sa BeforeThreadDone(), избавляет
     * от необходимости дублировать код.
     *
     * Для внесения специфичных действий необходимо перегрузить данный метод.
     *
     * @note Важно помнить: тело метода будет выполняться, всё еще,в адреадресном
     * пространстве потока, но не в адресном пространстве экземпляра
     * данного класса @sa FWThread!
     *
     * Данный метод будет вызван даже в случае возникновения исключительной
     * ситуации в методе @sa FWThread::run.
     */
    virtual void
    AfterThreadDone() {}

    /**
     * @brief Метод ThreadDone выполняет действия, связанные с завершением
     * работы потока.
     *
     * Этот метод нужно вызывать тогда, когда завершается работа потока без
     * участия @sa IsBreak (например, в конце процедуры потока).
     * Метод @sa IsBreak , при необходимости, вызовет этот метод сам.
     */
    void
    ThreadDone ();

  Q_SIGNALS:

    /**
     * @brief Сигнал sign_MachineChanged оповещает об изменении адреса объекта
     * КА после создания его в потоке или удаления (разрушения).
     * @param aPointer указатель на объект Конечного автомата (КА).
     */
    void
    sign_MachineChanged(const FWStateMachine* aPointer);

    /**
     * @brief Сигнал sign_MachineStarted оповещает о запуске КА на выполнение.
     * @note Является ретрансляцией сигнала @sa QStateMachine::started;
     */
    void
    sign_MachineStarted();

    /**
     * @brief Сигнал sign_MachineStopped оповещает о хавершении работы КА.
     * @note Является ретрансляцией сигнала @sa QStateMachine::finished;
     */
    void
    sign_MachineStopped();

    /**
     * @brief Сигнал sign_MachinePaused оповещает о приостановке работы КА.
     */
    void
    sign_MachinePaused ();

    /**
     * @brief Сигнал sign_MachineResumed оповещает о возобновлении работы КА.
     */
    void
    sign_MachineResumed ();

    /**
     * @brief Сигнал sign_ObjectAddress ретранслирует сигнал @sa FWItem::sign_ObjectAddress
     * от объектов, принадлежащих адресному пространству потока выполнения.
     * @param aMyAddress указатель на объект (значение адреса)
     */
    void
    sign_ObjectAddress (QObject* aMyAddress);

  public Q_SLOTS:

    /**
     * @brief Слот slot_AfterMachineStart позволяет выполнить некоторые действия
     * после запуска КА на выполнение.
     *
     * Действия, описанные данным слотом, выполняться лишь после запуска цикла
     * обработчика события потока методом @sa QThread::exec. Таким образом, метод
     * @sa FWStateMachine::start не запустит данный слот на выполнение до
     * вызова @sa QThread::exec.
     *
     * Перегруженный в данном классе метод QThread::run сперва вызывает метод
     * @sa FWStateMachine::start, а затем и  @sa QThread::exec.
     *
     * @note Важно помнить: тело метода будет выполняться в адреадресном
     * пространстве потока, но не в адресном пространстве экземпляра
     * данного класса @sa FWThread!
     */
    virtual void
    slot_AfterMachineStart () { if(!IsError ()) SetWorking (); }

    /**
     * @brief Слот slot_Pause запускает на выполнение
     * слот @sa FWStateMachine::slot_Pause.
     */
    void
    slot_Pause ();

    /**
     * @brief Слот slot_Resume  запускает на выполнение
     * слот @sa FWStateMachine::slot_Resume
     */
    void
    slot_Resume ();
};

//------------------------------------------------------------------------------

FW_END_NAMESPACE

Q_DECLARE_OPERATORS_FOR_FLAGS(FW_NAMESPACE::FWThread::FWThreadFlags)

#endif // FW_THREAD_H


FWThread.cpp

#include "FWThread.h"
#include "FWStateMachine.h"
#include <FWItem>
#include <QException>

FW_BEGIN_NAMESPACE

//------------------------------------------------------------------------------
unsigned long FW_THREAD_WAIT_TIMEOUT  = 7000;

//------------------------------------------------------------------------------
/**
 * @brief Класс FWThread_helper представляет воспомогательные действия по
 * корректному завершению рабочего потока класса @sa FQThread.
 *
 * Объект класса формируется в адресном пространстве рабочего потока объекта
 * класса @sa FQThread. Для этого переопределяется метод @sa Qthread::run :
 * @<code>
 * FWThread::run()
 * {
 *    QScopedPointer<FWThread_helper> v_helper(new FWThread_helper(this));
 *
 * }
 * @</code>
 *
 * "Умный" указатель v_helper на объект класса @sa FWThread_helper гарантирует
 * разрушение объекта после завершения выполнения рабочего потока даже при
 * возникновении исключительной ситуации.
 *
 * Класс опознаёт поток-хозяин типа @sa FWThread по содержимому атрибута
 * @sa m_ext_thread.
 *
 * В классе объявлен атрибут @sa m_fsm, представляющий детерминированный
 * конечный автомат типа @sa FWStateMachine как потомок класса @sa QStateMachine.
 *
 * Деструктор @sa ~FWThread_helper класса произведёт корректный останов
 * конечного автомата и разрушение всех зависимых объектов из контейнера
 * @sa FWThread_helper::m_objects_container.
 *
 * @warning Необходимо помнить, что указатель @sa FWThread_helper::m_ext_thread
 * на объект класса @sa FWThread остаётся неизменным. Разрушение объекта-потока
 * в даннолм классе не происходит. Поток можно перезапустить на выполнение.
 */
class
  #ifdef FW_LIBRARY
      FW_SHARED_EXPORT
  #else
      Q_DECL_EXPORT
  #endif
FWThread_helper : public QObject
{
//    Q_OBJECT

    /**
     * @brief Тип TSharedObject представляет ссылку на объект класса @sa QObject
     * как общий "умный" указатель.
     *
     * @see QSharedPointer, QObject
     */
    typedef QSharedPointer<QObject> TSharedObject;

    /**
     * @brief Тип TObjectsList определяет структуру списка объектов, используемых
     * рабочим потоком объекта @sa m_ext_thread.
     *
     * @value TObjectsList::at(<i>) обвёрнутый типом @sa TSharedObject указатель
     * на объект, используемый рабочим потоком атрибута @sa m_ext_thread.
     */
    typedef QList<TSharedObject>  TObjectsList;

  private:
    /**
     * @brief Атрибут m_ext_thread содержит ссылку на объект @sa FWThread.
     */
    QPointer<FWThread> m_ext_thread;

    /**
     * @brief Атрибут m_fsm представляет детерминированный конечный автомат,
     * управляющий последовательностью действий в рабочем потоке объекта
     * @sa m_ext_thread.
     */
    QPointer<FWStateMachine>  m_fsm;

    /**
     * @brief Атрибут m_objects_container представляет список указателей типа
     * @sa TSharedObject на внешние объекты, используемые потоком атрибута
     * @sa m_ext_thread.
     *
     * @note Объекты перемещены в адресноe пространство потока выполнения класса
     * @sa FWThread по значению указателя атрибута @sa m_ext_thread.
     */
    TObjectsList  m_objects_container;

    /**
     * @static
     * @brief Статический метод OnDeleteLater определяет деструктор для
     * очередного объекта из контейнера @sa m_objects_container, используемого
     * рабочим потоком атрибута @sa m_ext_thread.
     * @param aObject Указатель на объект.
     */
    static
    void OnDeleteLater(QObject* aObject)
    {
      if(NOT_NULL(aObject))
        aObject->deleteLater();
    }

  public:

    /**
     * @brief Метод ClearObjects очищает список объектов, используемых
     * рабочим потоком атрибута @sa m_ext_thread.
     */
    void
    ClearObjects ()
    {
      Q_FOREACH(TSharedObject v_obj, m_objects_container)
          v_obj.reset();
      m_objects_container.clear ();
    }

    /**
     * @brief Метод LocateObject проверяет вхождения объекта в контейнер.
     * @param aObject Указатель на проверяемый объект.
     * @return Булево значение:
     * @value TRUE указатель на объект в контейнере;
     * @value FALSE указатель на объект не размещен в контейнере.
     */
    bool
    LocateObject (const QObject* aObject) const
    {
      Q_FOREACH(TSharedObject v_obj, m_objects_container)
          if(EQUAL(v_obj.data (),aObject))
            return true;
      return false;
    }

    /**
     * @brief Конструктор FWThread_helper класса @sa FWThread_helper.
     * @param aThread Указатель на обслуживаемый классом @sa FWThread_helper
     * объекта класса @sa FWThread.
     *
     * @warning Параметр @sa aThread не должен быть NULL, т.к. теряется весь
     * смысл объекта данного класса.
     */
    explicit
    FWThread_helper(FWThread* aThread)
      : QObject     (Q_NULLPTR)
      , m_ext_thread(aThread)
      , m_fsm       (new FWStateMachine)
    {
      Q_ASSERT(NOT_NULL(m_ext_thread));
      m_objects_container.clear ();
    }

    /**
     * @brief Деструктор класса @sa FWThread_helper.
     *
     * Деструктор вызывается перед выходом из метода @sa FWThread::run.
     * Деструктор обеспечивает корректное удаление объектов, используемых
     * рабочим потоком объекта @sa m_ext_thread.
     *
     * Деструктор выполняет набор методов класса @sa FWThread перед какой-либо
     * точкой выхода из потока (прерывание, корректное завершение, выход при
     * возникновении исключительной ситуации)
     */
    ~FWThread_helper()
    {
      if(NOT_NULL(m_ext_thread))
      {
        m_ext_thread->BeforeThreadDone ();
        m_ext_thread->ThreadDone ();
        m_ext_thread->AfterThreadDone ();
      }
      ClearObjects ();
      m_fsm->deleteLater ();
    }

    /**
     * @brief Метод Machine представляет указатель на детерминированный конечный
     * автомат (КА).
     * @return указатель на объект конечного автомата.
     *
     * Объект КА расположен в адресном пространстве рабочего потока, а не
     * объекта класса @sa FWThread.
     */
    FWStateMachine*
    Machine () const { return m_fsm; }

    /**
     * @brief Метод InsertObject добавляет объект класса @sa QObject к потоку
     * выполнения.
     * @param aObject Указатель на объект.
     *
     * Если класс объекта наследуется от класса @sa FWItem фреймворка, то
     * происходит переадресация сигналов запроса указателя на объект.
     *
     * @note Для функции рабочего потока необходимо позаботится о проверке
     * флага @sa FWItem::SF_Active методом @sa FWItem::IsActive, чтобы
     * выйти из неё в какой-либо момент. Cлот @sa FWItem::slot_BreakProcess
     * подключён к сигналу @sa FWStateMachine::finished, что даёт возможность
     * установки флага активности @sa FWItem::SF_Active в нерабочее состояние
     * при вызове переопредеоённых методов @sa FWThread::quit
     * и @sa FWThread::terminate.
     */
    void
    InsertObject (QObject* aObject)
    {
      if(NOT_NULL(aObject) && (!LocateObject(aObject)) )
      {
        aObject->setParent (Q_NULLPTR);
        aObject->moveToThread (m_ext_thread);

        if( aObject->inherits (FW_CLASS_STR(FWItem).toLatin1 ()) )
        {
          // делегирование сигнала запроса адреса объекта
          QObject::connect (qobject_cast<FWItem*>(aObject),
                            &FWItem::   sign_ObjectAddress,
                            m_ext_thread,
                            &FWThread:: sign_ObjectAddress,
                            Qt::ConnectionType(Qt::DirectConnection |
                                               Qt::UniqueConnection));
        }

        m_objects_container.append (TSharedObject(aObject,OnDeleteLater));
      }
    }
};

//------------------------------------------------------------------------------
FWThread::FWThread(const bool aAutoDelete, QObject* aParent)
  : BaseClass (aParent)
  , m_helper  (Q_NULLPTR)
{
  SetEmpty ();
  if(aAutoDelete && IS_NULL(aParent))
    SetAutoDelete ();
}

//------------------------------------------------------------------------------
FWThread::~FWThread()
{
  quit ();
  if(!wait (FW_THREAD_WAIT_TIMEOUT))
  {
    // ну, очень нехороший случай...
    terminate ();
    if(!wait(FW_THREAD_WAIT_TIMEOUT))
      exit(EXIT_FAILURE);
  }
}

//------------------------------------------------------------------------------
void
FWThread::setParent(QObject* aParent)
{
  BaseClass::setParent (aParent);

  bool is_autodelete = bool(Flags () | THF_AutoDelete);
  if(IS_NULL(aParent) && (!is_autodelete))
    SetAutoDelete ();
  else
    if(NOT_NULL(aParent) && (is_autodelete))
      UnsetAutoDelete ();

  if(!IsAutoDeleted ())
    disconnect( this, &QThread::finished, this, &QThread::deleteLater);
}

//------------------------------------------------------------------------------
FWStateMachine*
FWThread::Machine()
{
  return m_helper.isNull () ? Q_NULLPTR : m_helper->Machine ();
}

//------------------------------------------------------------------------------
bool
FWThread::IsValid()
{
  return
      (!IsError()) &&
      (MachineExists () ? Machine ()->IsValid () : false);
}

//------------------------------------------------------------------------------
bool
FWThread::AttachObject(QObject* aObject)
{
  bool v_retval = !m_helper.isNull ();
  if(v_retval)
    m_helper->InsertObject (aObject);

  return v_retval;
}

//------------------------------------------------------------------------------
void
FWThread::terminate()
{
  ThreadDone ();
  BaseClass::terminate ();
}

//------------------------------------------------------------------------------
void
FWThread::quit()
{
  ThreadDone ();
  BaseClass::quit ();
}

//------------------------------------------------------------------------------
void
FWThread::Reconnect()
{
  if(IsAutoDeleted ())
    // Уничтожение экземпляра потока после завершения работы
    connect( this, &QThread::finished,
             this, &QThread::deleteLater,
             Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection));

  if(MachineExists ())
  {
    // Завершение цикла обработки событий потока при достижении конечным
    // автоматом состояния завершения работы
    connect( Machine (),&FWStateMachine:: finished,
             this,      &QThread::        quit,
             Qt::ConnectionType(Qt::AutoConnection | Qt::UniqueConnection));

    // Завершение работы конечного автомата после согнала потока о
    // завершении работы.
    connect( this,      &QThread::        finished,
             Machine (),&FWStateMachine:: stop,
             Qt::ConnectionType(Qt::AutoConnection | Qt::UniqueConnection));

    /// @name Немедленная ретрансляция сигналов конечного автомата из @sa FWThread
    /// @{
    connect (Machine (),&FWStateMachine:: started,
             this,      &FWThread::       sign_MachineStarted,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));

    connect (Machine (),&FWStateMachine:: stopped,
             this,      &FWThread::       sign_MachineStopped,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));

    connect (Machine (),&FWStateMachine:: sign_Paused,
             this,      &FWThread::       sign_MachinePaused,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));

    connect (Machine (),&FWStateMachine:: sign_Resumed,
             this,      &FWThread::       sign_MachineResumed,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
    /// @}

    // обязать объект потока выполнить некоторые действия немедленно после
    // запуска rонечного автомата
    connect (Machine (),&FWStateMachine:: started,
             this,      &FWThread::       slot_AfterMachineStart,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  }
}

//------------------------------------------------------------------------------
void
FWThread::InternalRun()
{
  QException _e;

  QT_TRY
  {
    if(IsValid ())
    {
      /// подключение правил взаимодействий экземпляра класса и конечного автомата
      Reconnect ();

      SetReady ();

      emit sign_MachineChanged(Machine ());
    }
    else
      _e.raise ();
  }
  QT_CATCH(QException &e)
  {
    UnsetAutoDelete ();
    SetError ();
    qDebug() << qPrintable(e.what ());
  }
}

//------------------------------------------------------------------------------
void
FWThread::run()
{
  FWThread_helper* v_helper = new FWThread_helper(this);
  m_helper = v_helper;

  InternalRun ();

  if(IsValid ())
  {
    BeforeMachineStart ();

    /// Запуск детерминированного Конечного Автомата @sa Machine ().
    Machine ()->start ();

    /// Запуск циклa обработки событий потока. => Начало работы КА.
    BaseClass::exec ();
  }
  else
  {
    if(!IsError ()) SetError ();
    exit (FW_ERR_THREAD_INCOMPLETE);
  }

  m_helper.clear ();
}

//------------------------------------------------------------------------------
void
FWThread::ThreadDone()
{
  SetStop ();

  if(MachineExists ())
  {
    if(Machine ()->IsActive ())
      Machine ()->stop ();
//    QObject::disconnect (Machine ());
  }
}

//------------------------------------------------------------------------------
void
FWThread::slot_Pause()
{
  if(MachineExists())
    Machine ()->slot_Pause();
}

//------------------------------------------------------------------------------
void
FWThread::slot_Resume()
{
  if(MachineExists())
    Machine ()->slot_Resume();
}

//------------------------------------------------------------------------------

FW_END_NAMESPACE



Пока остановимся подробнее на виртуальном методе FWThread ::BeforeMachineStart (). Причтите комметрарии стиля doxygen в листинге файла FWThread.h.

Вот, почему больше не нужно перегружать метод FWThread::run()!
Достаточно изменять поведение КА в перегруженных методах FWStateMachine::BeforeMachineStart (), чтобы заставить поток выполнять то, что вы захотите.

Класс-помощник FWThread_helper некоторыми атрибутами и методами.
В нём теперь размещён конечный автомат и некоторый контейнер объектов, перемещаемых в адресное пространство рабочего потока.
Разрушение объекта класса FWThread_helper вызовет разрушение объекта конечного автомата, что, в свою очередь, приведёт к останову работы автомата даже при возникновении исключительной ситуации.

Шаг 4. События.


Здесь всё просто. Можно определить собственный класс на основе класса QEvent Qt, добавив к нему атрибут «идентификатор». Типы старше QEvent::User дают понять КА, с какими состояниями и переходами (фиксированными или пользовательскими) приходится иметь дело, а идентификатор отвечает за …идентификацию события.
Листинг описания класса FWEvent содержит комментарии по атрибутам и методам и послужит описанием реализации.

#ifndef FW_EVENT_H
#define FW_EVENT_H

#include <FWConstants>

FW_BEGIN_NAMESPACE

//------------------------------------------------------------------------------
class FWStateMachine;

//------------------------------------------------------------------------------
/**
 * @brief Класс FWEvent описывает структуру событий, принятую в архитектуре
 * данного Фреймворка.
 *
 * Событие может обрабатываться внешним обработчиком (диспетчером) событий
 * или Конечным автоматом (далее -- КА) типа @sa FWStateMachine.
 * Любой объект-наследник класса @sa FWItem может отправлять событие данного
 * формата.
 * Любой объект-наследник класса @sa FWStateMachine КА также может отправлять
 * событие данного формата.
 * Отсутствие указателя на КА-родитель события формирует признак события как
 * системное. Метод @sa FWEvent::IsSystem указывает на данный признак.
 *
 * Объекты класса @sa FWTransition представляют переходы между
 * состояниями КА. Перегруженные методы @sa FWTransition::testEvent отсекают
 * события, не связанные с указанным свойством @sa FWEvent::Machine КА.
 */
class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWEvent : public QEvent
{
    Q_GADGET
    Q_ENUMS(FWEventType)

  //----------------------------------------------------------------------------
  private:

    /**
     * @brief Атрибут m_id содержит идентификатор события конкретного типа.
     */
    FWIdentificator m_id;

    /**
     * @brief Атрибут m_params содержит список значений параметров для метода
     * объекта (если таковой назначен), вызываемого обработчиком события.
     */
    QVariantList m_params;

  //----------------------------------------------------------------------------
  public:

    /**
     * @brief Перечисление FWEvent::FWEvents определяет типы событий для
     * работы Конечного автомата.
     */
    enum FWEventType
    {
      ET_SYSTEM,  ///< тип системного события

      /// @name Специфичные для @sa FWEvent Типы событий
      /// @{
      ET_GLOBAL   = QEvent::User  + 1,  ///< тип внешнего (но не системного) события
      ET_INTERNAL = ET_GLOBAL     + 1,  ///< тип служебного события
//TODO: Тип события в разработке...
//      ET_SIGNAL   = ET_INTERNAL + 1,  ///< тип события "Сигнал"
      ET_TASK     = ET_INTERNAL   + 1,  ///< тип события текущей задачи

      /// @}
      ET_MAX      = ET_TASK       + 1,  ///< тип сигнализатор допустимой верхней
                                      ///< границы типов событий
    };

    //--------------------------------------------------------------------------
    /// @name Конструкторы и деструктор класса.
    /// @{

    /**
     * @brief Конструктор FWEvent создаёт инициализированный объект данного класса.
     * @param aEvent Тип события;
     * @param aID Идентификатор события. Для системных событий устанавливается в
     * значение @sa EMPTY_ID.
     * @param aParams Некий дополнительный набор параметров.
     *
     * @note При вызове конструктора без параметров, производится инициализация
     * атрибутов значениями по-умолчанию: неиспользуемым типом события и
     * пустыми ссылками для всех атрибутов-указателей. При этом объект не несёт
     * смысловой нагрузки.
     *
     * @see QEvent, QEvent::Type, FWIdentificator, QVariantList.
     */
    explicit
    FWEvent(QEvent::Type        aEvent,
            FWIdentificator     aID     = EMPTY_ID,
            const QVariantList& aParams = QVariantList())
      : QEvent(aEvent)
      , m_id  (aID)
    {
      SetParams (aParams);
    }

    /**
     * @brief Конструктор FWEvent создаёт инициализированный объект данного
     * класса.
     * @param aEvent Тип события, определённый в терминах @sa FWEvent::FWEventType;
     * @param aID Идентификатор события;
     * @param aParams Некий дополнительный набор параметров.
     */
    explicit
    FWEvent(FWEvent::FWEventType  aEvent,
            FWIdentificator       aID,
            const QVariantList&   aParams = QVariantList())
      :FWEvent(static_cast<QEvent::Type>(aEvent), aID, aParams)
    {}

    /**
     * @brief Конструктор FWEvent с копированием значений некоторого события.
     * @param aEvent Cсылка на объект-собфтие источник.
     */
    explicit
    FWEvent(const FWEvent& aEvent)
      : FWEvent (aEvent.type (), aEvent.Identificator (), aEvent.Params ())
    {}

    /// @}

    //--------------------------------------------------------------------------
    /**
     * @brief Метод AsBaseClass приводит указатель на объект класса к указателю
     * типа базового класса.
     * @return Указатель на объект класса как базовый типа QEvent.
     */
    QEvent*
    AsBaseClass () { return static_cast<QEvent*>(this); }

    static bool
    IsValidType (QEvent::Type aType)
    { return BETWEEN(int(aType),QEvent::None, ET_MAX); }

    /**
     * @brief Метод IsValid проверяет событие на принадлежность диапазону типов,
     * обслуживаемых классом.
     * @param aEvent Указатель на событие
     * @return Булево значение:
     * @value TRUE Событие удовлетворяет условиям класса;
     * @value FALSE Событие не удовлетворяет условиям класса.
     */
    static bool
    IsValid (QEvent* aEvent)
    {
      if(NOT_NULL(aEvent))
        return IsValidType(aEvent->type());
      return false;
    }

    /**
     * @static
     * @brief Метод IsType проверяет принадлежность типа события к типу из
     * набора @sa FWEvent::FWEventType.
     * @param aEvent Проверяемое событие;
     * @param aType Значение типа события для проверки.
     * @return Булево значение:
     * @value TRUE Событие принадлежит к типу для проверки;
     * @value FALSE Событие не принадлежит к типу для проверки;
     */
    static bool
    IsType (QEvent* aEvent, FWEvent::FWEventType aType)
    {
      bool v_retval = IsValid (aEvent);

      if(v_retval)
      {
        v_retval =
            BETWEEN (int(aEvent->type ()),QEvent::None,ET_GLOBAL) ||
            EQUAL   (aType,ET_SYSTEM);

        if(!v_retval)
          v_retval =  EQUAL(static_cast<FWEvent::FWEventType>(aEvent->type ()),
                           aType);
      }

      return v_retval;
    }

    /**
     * @brief Метод IsSystem проверки события на принадлежность к набору системных
     * событий (определённых в наборе @sa QEvent::Type до QEvent::User)
     * @param aEvent Проверяемое событие.
     * @return Булево значение:
     * @value TRUE Событие системное;
     * @value FALSE Событие не системное.
     */
    static bool
    IsSystem (QEvent* aEvent)
    { return IsType (aEvent, ET_SYSTEM); }

    /**
     * @brief Метод IsGlobal проверки события на принадлежность к набору общих
     * событий, определённых как @sa FWEventType::ET_GLOBAL
     * @param aEvent Проверяемое событие.
     * @return Булево значение:
     * @value TRUE Событие общего назначения;
     * @value FALSE Событие не общего назначения.
     */
    static bool
    IsGlobal (QEvent* aEvent)
    { return IsType (aEvent, ET_GLOBAL); }

    /**
     * @brief Метод IsGlobal проверки события на принадлежность к набору служебных
     * событий, определённых как @sa FWEventType::ET_INTERNAL
     * @param aEvent Проверяемое событие.
     * @return Булево значение:
     * @value TRUE Событие служебное;
     * @value FALSE Событие не служебное.
     */
    static bool
    IsInternal (QEvent* aEvent)
    { return IsType (aEvent, ET_INTERNAL); }

    /**
     * @brief Метод IsGlobal проверки события на принадлежность к набору
     * событий отдельных задач, определённых как @sa FWEventType::ET_TASK
     * @param aEvent Проверяемое событие.
     * @return Булево значение:
     * @value TRUE Событие из набра задач;
     * @value FALSE Событие не из набра задач.
     */
    static bool
    IsTask (QEvent* aEvent)
    { return IsType (aEvent, ET_TASK); }

    /**
     * @brief Метод Identificator отображает идентификатор события
     * конкретного типа @sa FWEvent::FWEventType
     * @return Значение идентификатора события.
     */
    inline FWIdentificator
    Identificator () const { return m_id; }

    inline QVariantList
    Params () const { return m_params; }

    inline void
    SetIdentificator (const FWIdentificator aID)
    {
      if(NOT_EQUAL(Identificator (), aID) )
        m_id = aID;
    }

    inline void
    SetParams (const QVariantList& aParams)
    {
      m_params.clear ();
      m_params.append (aParams);
    }

    inline virtual bool
    IsValid ()
    { return IsValid (AsBaseClass ()) && NOT_EQUAL(Identificator (),EMPTY_ID); }

    virtual FWEvent&
    operator = (const FWEvent& aFrom)
    {
      AsBaseClass ()->operator = (aFrom);

      SetIdentificator(aFrom.Identificator ());
      SetParams       (aFrom.Params ());

      return (*this);
    }

    inline virtual bool
    operator == (const FWEvent& aFrom)
    { return
          EQUAL(type (),          aFrom.type ()         ) &&
          EQUAL(Identificator (), aFrom.Identificator ());
    }

    inline virtual bool
    operator == (const QEvent& aFrom)
    { return EQUAL(type (), aFrom.type ());  }

    inline virtual bool
    operator != (const FWEvent& aFrom) { return NOT_EQUAL(*this, aFrom); }

    inline virtual bool
    operator != (const QEvent& aFrom) { return NOT_EQUAL(*this, aFrom); }
};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWSystemEvent : public FWEvent
{
    Q_GADGET
  public:
    explicit
    FWSystemEvent(FWIdentificator     aID,
                  const QVariantList& aParams = QVariantList())
      : FWEvent(FWEvent::ET_SYSTEM, aID, aParams)
    { Q_ASSERT(IsSystem (AsBaseClass ())); }
};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWGlobalEvent : public FWEvent
{
    Q_GADGET
  public:
    explicit
    FWGlobalEvent(FWIdentificator     aID,
                  const QVariantList& aParams = QVariantList())
      : FWEvent(FWEventType::ET_GLOBAL, aID, aParams) {}
};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWInternalEvent : public FWEvent
{
    Q_GADGET
    Q_ENUMS(FWInternalIdentificators)

  public:

    enum FWInternalIdentificators
    {
      OEI_None  = FWIdentificator(0x00),
      OEI_Start = FWIdentificator(0x01),
      OEI_Stop  = FWIdentificator(0x02),
      OEI_Pause = FWIdentificator(0x04),
      OEI_Resume= FWIdentificator(0x08),
      OEI_Log   = FWIdentificator(0x10),
      OEI_Error = FWIdentificator(0x20),
    };

    explicit
    FWInternalEvent(FWIdentificator     aID     = OEI_None,
                    const QVariantList& aParams = QVariantList())
      : FWEvent(FWEventType::ET_INTERNAL, aID, aParams)
    {}

    bool
    IsValid () Q_DECL_OVERRIDE
    { return FWEvent::IsValid () && NOT_EQUAL(Identificator (),OEI_None); }
};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWTaskEvent : public FWEvent
{
    Q_GADGET
  public:
    enum FWUsbTask
    {
      TEI_None      = FWIdentificator(0),
      TEI_Open,
      TEI_Opened,
      TEI_Close,
      TEI_Closed,
      TEI_NewStatus,
      TEI_NewFileName,
      TEI_Write,
      TEI_Writed,
      TEI_Read,
      TEI_Readed,
      TEI_Log,
      TEI_Error,
    };

    explicit
    FWTaskEvent(FWIdentificator     aID     = EMPTY_ID,
                const QVariantList& aParams = QVariantList())
      : FWEvent(FWEventType::ET_TASK, aID, aParams)
    {}

    bool
    IsValid () Q_DECL_OVERRIDE
    { return FWEvent::IsValid () && NOT_EQUAL(Identificator (),EMPTY_ID); }
};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWUsbEvent : public FWTaskEvent
{
    Q_GADGET

  private:
    quint16 m_VID;
    quint16 m_PID;
    quint8  m_BusNumber;
    quint8  m_Address;
    quint8  m_Port;

  public:

    enum FWUsbTask
    {
      UEI_None      = FWIdentificator(0),
      UEI_Attached,
      UEI_Detached,
      UEI_ContextChanged,
      UEI_Open,
      UEI_Opened,
      UEI_Close,
      UEI_Closed,
      UEI_WriteBulk,
      UEI_WriteBulkComplete,
      UEI_ReadBulk,
      UEI_ReadBulkComplete,
      UEI_Log,
      UEI_Warning,
      UEI_Error,
    };

    explicit
    FWUsbEvent(quint16              aVendorID,
               quint16              aProductID,
               quint8               aBusNumber,
               quint8               aAddress,
               quint8               aPort,
               FWIdentificator      aID     = FWUsbEvent::UEI_None,
               const QVariantList&  aParams = QVariantList())
      : FWTaskEvent (FWIdentificator(aID), aParams)
      , m_VID       (aVendorID)
      , m_PID       (aProductID)
      , m_BusNumber (aBusNumber)
      , m_Address   (aAddress)
      , m_Port      (aPort)
    {}

    explicit
    FWUsbEvent(FWIdentificator      aID,
               const QVariantList&  aParams = QVariantList())
      : FWUsbEvent(0,0,0,0,0,aID,aParams)
    {}

    inline quint16  VID       () const { return m_VID; }
    inline quint16  PID       () const { return m_PID; }
    inline quint8   BusNumner () const { return m_BusNumber; }
    inline quint8   Address   () const { return m_Address; }
    inline quint8   Port      () const { return m_Port; }

    FWUsbEvent&
    operator = (const FWUsbEvent& aFrom)
    {
      FWEvent::operator = (static_cast<FWEvent>(aFrom));
      m_VID       = aFrom.VID ();
      m_PID       = aFrom.PID ();
      m_BusNumber = aFrom.BusNumner ();
      m_Address   = aFrom.Address ();
      m_Port      = aFrom.Port ();
      return (*this);
    }

    bool
    operator == (const FWUsbEvent& aFrom)
    { return
          FWEvent::operator == (static_cast<FWEvent>(aFrom)) &&
          EQUAL(VID (), aFrom.VID ()) &&
          EQUAL(PID (), aFrom.PID ()) &&
          EQUAL(BusNumner (), aFrom.BusNumner ()) &&
          EQUAL(Address (), aFrom.Address ()) &&
          EQUAL(Port (), aFrom.Port ());
    }
};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
///
/// @brief FWEventTypeMap
/// @see FWEvent, QEvent, QEvent::Type
///
typedef QMap<FWEvent::FWEventType, FWEvent*> FWEventTypeMap;

typedef QMapIterator<FWEvent::FWEventType, FWEvent*> FWEventTypeMap_I1;
typedef FWEventTypeMap::Iterator FWEventTypeMap_I2;
typedef FWEventTypeMap::ConstIterator FWEventTypeMap_CI;

///
/// @brief FWEventMap
/// @see FWEvent, QEvent, QEvent::Type, FWIdentificator
///
typedef QMap<FWIdentificator, FWEvent*> FWEventMap;

typedef QMapIterator<FWIdentificator, FWEvent*> FWEventMap_I1;
typedef FWEventMap::Iterator FWEventMap_I2;
typedef FWEventMap::ConstIterator FWEventMap_CI;
//------------------------------------------------------------------------------

FW_END_NAMESPACE

//------------------------------------------------------------------------------
Q_DECLARE_METATYPE(FW_NAMESPACE::FWEventTypeMap)
Q_DECLARE_METATYPE(FW_NAMESPACE::FWEventMap)
//------------------------------------------------------------------------------

#endif // FW_EVENT_H


Так, для управления переходами между состояниями потока, определёнными в теле класса FWStateMashine, применяются события класса FWInternalEvent, пользовательские (добавляемые) переходы используют класс FWTskEvent.

Используемый КА реализует модель автомата Мура, где каждое состояние отвечает за уникальное значение выхода. В примере книги А. Н. Боровского «Qt4.7+.Практическое программирование на C++» показано, как реализовать модель автомата Мили, где мы должны знать, откуда пришли, на основании каких данных и какой за набор выходных сигналов и состояний должно быть на выходе.

Воспользуемся полезным методом QAbstractTransition::eventTest(QEvent* aEvent) для фильтрации собственных событий класса,
перегрузив его:

bool
FWTransition::eventTest(QEvent* aEvent)
{
  bool v_retval = FWEvent::IsValid (aEvent);

  if(v_retval)
    v_retval = EQUAL(aEvent->type (),EventType ());

  if(v_retval)
  {
      FWEvent* v_e = static_cast<FWEvent*>(aEvent);
      v_retval =
          EQUAL(v_e->type (),         EventType ()) &&
          EQUAL(v_e->Identificator (),EventID   ());
  }

  return v_retval;
}


Метод отсекает все ненужные события и позволяет выполнить переход лишь по событию определённого типа и идентификатора, нга который настроен конкретный переход класса FWTransition КА.

Предварительный итог.


  • Создан класс на основе QThread, позволяющий выполнять алгоритм рабочего потока на основе модели конечного автомата.
  • Класс универсален к применению различных алгоритмов работы конечных автоматов в едином контексте базовой модели, управляющей потоком.
  • При создании потомков на базе FWThread не требуется переопределения всех методов. Достаточно переопределить один метод, запускаемый перед работой КА, для задания собственного графа переходов и состояний.
  • Избавиться от цикла в рабочем потоке с проверками всевозможных условий для обнаружения точки выхода из потока.
  • Компактный код реализации класса управления потоком.


Шаг 5. Пример. Сканер подключённых USB-устройств с применением библиотеки libUSB.


Пришло время продемонстрировать пример реализации связки «поток + конечный автомат».
Я применил эту технологию для работы с USB-устройствами.
Некоторые устройства не поддерживают методы оповещения подключения и отключения средствами API библиотеки libUSBx. Поэтому приходится всё время формировать новый список активных устройств и на основании его смотреть, появилось ли новое или отключилось ли некоторое USB-устройство из предыдущего списка.

Хочу подробнее остановиться на контексте библиотеки libUSB типа libusb_context . К сожалению, описанию контекста в сетевых ресурсах информации уделено недостаточно внимания.

Весьма полезно иногда разделить несколько задач приложения при работе с USB-устройствами на несколько сценариев. В нашем случае один поток проверяет список активных на данный момент USB-устройств, а остальные работают с конкретными устройствами в дополнительных потоках. При использовании всеми потоками одного контекста после инициализации сессии библиотечной функцией libusb_init(NULL) возникает критическая секция с разделяемым ресурсом контекста. Приходится создавать много лишнего кода для проверки состояния сессии. К тому же, разделение одного контекста между всеми потоками замедлит работу приложения.

Когда в качестве параметра указателя на контекст для функции libusb_init() передан NULL, будет использоваться контекст по умолчанию. Контекст по умолчанию создается первый раз функцией libusb_init (), когда никакого другого контекста нет.
Библиотека libUSB написана так, чтобы можно было запустить несколько пользовательских сценариев. Все «экземпляры» libUSB, созданные в разных контекстах не будут мешать друг другу: функция libusb_open (), вызванная в одной сессии контекста от одного потока не повлияет на те же параметры для других потоков.
Если одна сессия контекста библиотеки вызовет libusb_exit (), другие потоки могут продолжать использовать методы API libUSB, пока не закроют свои контексты функцией libusb_exit ().

Я не использую напрямую адреса устройств, полученные вызовом libusb_get_device_list().
И уж, тем более, ни в коем случае нельзя передавать адреса устройств типа libusb_device из сессии одного контекста в поток, использующий собственный контекст.
Идентифицироать USB-устройство можно при помощи пяти значений:
  1. VID:
  2. PID;
  3. Номер шины;
  4. Значение адреса;
  5. Номер порта.


Значения VID и PID извлекаются из описателя дескриптора типа libusb_device_descriptor, а остальные формируются функциями libusb_get_bus_number(), libusb_get_device_address() и libusb_get_port_number().
Возможность создания собственного класса события, унаследованного от QEvent, позволяет передать конечному автомату все необходимые значения.
Повтор части кода

class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWUsbEvent : public FWTaskEvent
{
    Q_GADGET

  private:
    quint16 m_VID;
    quint16 m_PID;
    quint8  m_BusNumber;
    quint8  m_Address;
    quint8  m_Port;

  public:

    enum FWUsbTask
    {
      UEI_None      = FWIdentificator(0),
      UEI_Attached,
      UEI_Detached,
      UEI_ContextChanged,
      UEI_Open,
      UEI_Opened,
      UEI_Close,
      UEI_Closed,
      UEI_WriteBulk,
      UEI_WriteBulkComplete,
      UEI_ReadBulk,
      UEI_ReadBulkComplete,
      UEI_Log,
      UEI_Warning,
      UEI_Error,
    };

    explicit
    FWUsbEvent(quint16              aVendorID,
               quint16              aProductID,
               quint8               aBusNumber,
               quint8               aAddress,
               quint8               aPort,
               FWIdentificator      aID     = FWUsbEvent::UEI_None,
               const QVariantList&  aParams = QVariantList())
      : FWTaskEvent (FWIdentificator(aID), aParams)
      , m_VID       (aVendorID)
      , m_PID       (aProductID)
      , m_BusNumber (aBusNumber)
      , m_Address   (aAddress)
      , m_Port      (aPort)
    {}

    explicit
    FWUsbEvent(FWIdentificator      aID,
               const QVariantList&  aParams = QVariantList())
      : FWUsbEvent(0,0,0,0,0,aID,aParams)
    {}

    inline quint16  VID       () const { return m_VID; }
    inline quint16  PID       () const { return m_PID; }
    inline quint8   BusNumner () const { return m_BusNumber; }
    inline quint8   Address   () const { return m_Address; }
    inline quint8   Port      () const { return m_Port; }

    FWUsbEvent&
    operator = (const FWUsbEvent& aFrom)
    {
      FWEvent::operator = (static_cast<FWEvent>(aFrom));
      m_VID       = aFrom.VID ();
      m_PID       = aFrom.PID ();
      m_BusNumber = aFrom.BusNumner ();
      m_Address   = aFrom.Address ();
      m_Port      = aFrom.Port ();
      return (*this);
    }

    bool
    operator == (const FWUsbEvent& aFrom)
    { return
          FWEvent::operator == (static_cast<FWEvent>(aFrom)) &&
          EQUAL(VID (), aFrom.VID ()) &&
          EQUAL(PID (), aFrom.PID ()) &&
          EQUAL(BusNumner (), aFrom.BusNumner ()) &&
          EQUAL(Address (), aFrom.Address ()) &&
          EQUAL(Port (), aFrom.Port ());
    }
};



Для сканирования устройств созданы два класса FWUscbInstance и его предок FWExecuteInstance. Класс FWExecuteInstance наследует FWDocument < — FWItem.
FWExecuteInstance.h

#ifndef FW_EXECUTE_INSTANCE_H
#define FW_EXECUTE_INSTANCE_H

#include <FWDocument>

FW_BEGIN_NAMESPACE

//------------------------------------------------------------------------------
class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWExecuteInstance : public FWDocument
{
    Q_OBJECT

    Q_CLASSINFO("brief",          "FW Execute Instance Class")
    Q_CLASSINFO("created",        "12-MAY-2015")
    Q_CLASSINFO("modified",       "12-MAY-2015")
    //
    Q_CLASSINFO("project",        "Common Qt-based Framework")

    /// @name Локальные типы класса.
    /// @{

    typedef FWDocument BaseClass;

    /// @}

  private:
    ///
    /// @brief m_timer
    ///
    QTimer m_timer;

    ///
    /// @brief m_timer_mode
    ///
    bool m_timer_mode;

  public:
    Q_INVOKABLE explicit
    FWExecuteInstance(bool      aTimerMode = false,
                      QObject*  aParent    = Q_NULLPTR);

    bool
    IsTimerMode() const { return m_timer_mode; }

    void
    Open () Q_DECL_OVERRIDE;

    void
    Close () Q_DECL_OVERRIDE;

    inline void
    StartTimer (int aMSec = 10)
    {
      if((!m_timer.isActive ()) && IsTimerMode ())
        m_timer.start (aMSec);
    }

    inline void
    StopTimer ()
    {
    if(m_timer.isActive ())
      m_timer.stop ();
    }

    inline bool
    IsTiming () const
    { return m_timer.isActive () && IsTimerMode (); }

    void
    SetTimerMode (bool aTimerMode)
    {
      if(EQUAL(m_timer_mode, aTimerMode))
        return;

      bool is_timing = IsTiming ();
      if(is_timing)
        StopTimer ();
      m_timer_mode = aTimerMode;
      if(is_timing || IsTimerMode ())
        StartTimer ();
    }

  public slots:

    virtual void
    slot_OnExecute () {}

    void
    slot_BreakProcess () Q_DECL_OVERRIDE;

    virtual void
    slot_ResumeProcess ()
    {
      Open ();//SetStatus (SF_Active);
      if(IsTimerMode ())
        StartTimer ();
    }
};
//------------------------------------------------------------------------------

FW_END_NAMESPACE

#endif // FW_EXECUTE_INSTANCE_H


FWUscbInstance.h

#ifndef FW_USB_INSTANCE_H
#define FW_USB_INSTANCE_H

#include "FWUsbTypes.h"
#include "FWExecuteInstance.h"

FW_BEGIN_NAMESPACE

//------------------------------------------------------------------------------
class FWUsbInstance;
class FWUsbConnection;

//------------------------------------------------------------------------------
///
/// \language Russian
/// @brief Класс FWUsbInstance очередной определяет контекст подключения к
/// библиотеке libusb
///
/// При работе с библшиотекой libusb используется понятие libusb сессии.
/// Понятие индивидуальной libusb сессии позволяет программе использовать несколько
/// подключений к библиотеке libusb (или динамически загружать несколько модулей)
/// Это позволит избежать интерференции между отдельными пользователями
/// libusb - например, libusb_set_debug () не будет влиять на другого пользователя
/// библиотеки, и libusb_exit () не будет уничтожать ресурсы, которые другие
/// пользователи по-прежнему используют.
///
/// Сессии создаются libusb_init () и уничтожил через libusb_exit ().
///
class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWUsbInstance : public FWExecuteInstance
{
    Q_OBJECT

    Q_CLASSINFO("brief",          "FW LibUSB Instance Class")
    Q_CLASSINFO("created",        "27-MAR-2015")
    Q_CLASSINFO("modified",       "12-MAY-2015")
    //
    Q_CLASSINFO("project",        "Common Qt-based Framework")

    /// @name Локальные типы класса.
    /// @{

    typedef FWExecuteInstance BaseClass;

    /// @}

  private:
    /// @name Сервисные атрибуты внутреннего применения
    /// @{
    ///

    /// @brief m_context контекст подключения к libusb
    /// @see libusb_context, TUsbContext*
    ///
    TUsbContext*  m_context;

    ///
    /// @brief m_log_level уровень отладочной информации
    /// @see libusb_log_levelб TUsbLogLevel
    ///
    TUsbLogLevel    m_log_level;

    QList<QVector<int> > m_attached_list;
    /// @}

  public:

    /// @name Конструкторы и деструктор класса.
    /// @{

    Q_INVOKABLE explicit
    FWUsbInstance(bool          aTimerMode = false /* Thread or Manual*/,
                  TUsbLogLevel  aLogLevel  = LIBUSB_LOG_LEVEL_ERROR /*LIBUSB_LOG_LEVEL_DEBUG*/,
                  QObject*      aParent    = Q_NULLPTR);

    /// @}

    BaseClass*
    AsBaseClass () Q_DECL_OVERRIDE
    { return AsClass<BaseClass>(); }

    bool
    IsValid () const Q_DECL_OVERRIDE
    { return BaseClass::IsValid() && ContextExists (); }

    bool
    IsActive () const  Q_DECL_OVERRIDE
    { return IsValid() && BaseClass::IsActive(); }

    virtual void
    OpenUsbContext (bool aCloseOldContext = false);

    virtual void
    CloseUsbContext ();

    void
    SetContext (TUsbContext* aContext);

    virtual bool
    ContextExists () const { return NOT_ZERRO(UsbContextAsID ()); }

    TUsbContext*
    UsbContext () const { return m_context; }

    void
    MakeUsbDeviceList();

    void
    Open () Q_DECL_OVERRIDE;

    void
    Close () Q_DECL_OVERRIDE;

    void
    Reload ();

  protected:

    void
    Initialisation () Q_DECL_OVERRIDE;

    inline FWIdentificator
    UsbContextAsID () const { return FWIdentificator(UsbContext()); }

    void
    ClearUsbDeviceList ();

  Q_SIGNALS:

    void
    sign_ContextChanged (const QObject*);

    ///
    void
    sign_UsbDeviceAttached (quint8 aBus, quint8 aAddr, quint8 aPort,
                            quint16 aVendorID, quint16 aProductID);

    ///
    /// @brief sign_UsbDeviceDeleted
    /// @param aDevice
    ///
    void
    sign_UsbDeviceDetached(quint8 aBus, quint8 aAddr, quint8 aPort,
                           quint16 aVendorID, quint16 aProductID);

//    void
//    sign_DeviceListChanged (const TUsbDeviceList& aList,
//                            const FWUsbInstance*  aInstance);
//    void
//    sign_UsbDeviceStarted (const FWThreadController* aController);

  public Q_SLOTS:

    void
    slot_OnExecute () Q_DECL_OVERRIDE;

    void
    slot_OnPause ();

    void
    slot_OnResume ();

    bool
    slot_ApplyFilter () Q_DECL_OVERRIDE;

    void
    slot_OnLoop ();
};

//------------------------------------------------------------------------------
///
/// @brief FWSharedUsbInstance Тип @a QSharedPointer контейнер для
/// класса @a FWUsbInstance
///
typedef QSharedPointer<FWUsbInstance> FWSharedUsbInstance;

//------------------------------------------------------------------------------

FW_END_NAMESPACE

#endif // FW_USB_INSTANCE_H


Метод FWUsbInstance::Reload() класса FWUsbInstance сканирования формирует события подключения или отключения очередного USB-устройства.

void
FWUsbInstance::Reload()
{
  if(!IsActive()) return;

  libusb_set_debug (m_context, LIBUSB_LOG_LEVEL_NONE);

  // запомнить текущий список устройств
  QList<QVector<int> >  _list_prev = m_attached_list;

  // сформировать актуальный список устройств ...
  MakeUsbDeviceList();

  // ... и поместить в контейнер
  QList<QVector<int> >  _list_now = m_attached_list;

  // удаление отсоединённых устройств
  if(!_list_prev.isEmpty ())
    for(register int i = 0; i < _list_prev.size (); ++i)
      if(_list_now.indexOf (_list_prev.at (i)) < 0)
      {
        slot_OnMachineEvent(
              new FWUsbEvent(
                quint16(_list_prev.at (i).at (3)),//VID
                quint16(_list_prev.at (i).at (4)),//PID
                quint8 (_list_prev.at (i).at (0)),//Bus Number
                quint8 (_list_prev.at (i).at (1)),//Address
                quint8 (_list_prev.at (i).at (2)),//Port Number
                FWUsbEvent::UEI_Detached,));// идентификатор события
      }

  // добавление новых удаление устройств
  if(!_list_now.isEmpty ())
    for(register int i = 0; i < _list_now.size (); ++i)
      if(!_list_prev.contains (_list_now.at (i)))
      {
        slot_OnMachineEvent(
              new FWUsbEvent(
                quint16(_list_prev.at (i).at (3)),//VID
                quint16(_list_prev.at (i).at (4)),//PID
                quint8 (_list_prev.at (i).at (0)),//Bus Number
                quint8 (_list_prev.at (i).at (1)),//Address
                quint8 (_list_prev.at (i).at (2)),//Port Number
                FWUsbEvent::UEI_Attached));// идентификатор события
      }

  libusb_set_debug (m_context, m_log_level);
}



Вот, и всё…
Как было сказано выше, я использую наследование всех QObject-классов от собственного класса FWItem (наследника QObject), в котором определён слот slot_OnMachineEvent()
void
FWItem::slot_OnMachineEvent(FWEvent* aEvent)
{
  if(MachineExists())
    Machine()->PostEvent(aEvent);
}


Добавление состояний и переходов к конечному автомату потока.


Теперь можно воспользоваться технологией добавления собственного графа состояний и переходов к предопределённому графу потока.
Необходимо перегрузить метод FWThread::BeforeMachineStart()
Создадим класс-наследник FWThread и назовём его FWUsbScanner.
Определим метод FWUsbScanner:: BeforeMachineStart()

void
FWUsbScanner::BeforeMachineStart()
{
  if(!IsValid ()) return;

  if(m_usb.isNull())
    m_usb = new FWUsbInstance(m_timer_mode);
  m_usb->SetMachine(Machine());

  /// @name Пересылка сигналов о состоянии подключения USB-устройств
  /// @{
  QObject::connect(m_usb,&FWUsbInstance::sign_UsbDeviceAttached,
                   this, &FWUsbScanner:: sign_UsbDeviceAttached,
                   Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  QObject::connect(m_usb,&FWUsbInstance::sign_UsbDeviceDetached,
                   this, &FWUsbScanner:: sign_UsbDeviceDetached,
                   Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  QObject::connect(m_usb,&FWUsbInstance::sign_ContextChanged,
                   this, &FWUsbScanner:: sign_ContextChanged,
                   Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));

  /// @}

  // перемещение в адресное пространство рабочего потока
  if(AttachObject (m_usb.data()))
    QT_TRY
    {
      /// @name Добавление к КА алгоритма сканирования USB-устройств
      /// @{

      QState* sIdle  = Machine ()->StateIdle ();

      // добавление состояния КА "Открыть порт"
      FWState* sOpen  = new FWState(Machine ()->StateGroup ());

      // добавление состояния КА "Сканирование"
      FWState* sScan  = new FWState(Machine ()->StateGroup ());
      sScan->setObjectName("USB Scan");

      // переход на "Открыть порт" после запуска КА
      Machine()->AddBranch(sIdle, sOpen);
      QObject::connect(sOpen, &FWState::entered,
                       m_usb, &FWUsbInstance:: slot_Open,
                       Qt::ConnectionType(Qt::DirectConnection |
                                          Qt::UniqueConnection));

      // переход на "Сканирование" после запуска КА
      Machine()->AddBranch(FWTaskEvent::ET_TASK, FWTaskEvent::TEI_Opened,
                           sOpen, sScan);

      if(!m_usb->IsTimerMode())
      {
        // сканирование не по таймеру: разбить цикл проверки на отдельные
        // состояния и переходы
        FWState* sLoop  = new FWState(Machine ()->StateGroup ());
        Machine()->AddBranch(sScan, sLoop);
        Machine()->AddBranch(FWEvent::ET_TASK,FWTaskEvent::TEI_Read,
                  sLoop, sScan);
        QObject::connect(sLoop, &FWState::entered,
                         m_usb, &FWUsbInstance:: slot_OnLoop,
                         Qt::ConnectionType(Qt::DirectConnection |
                                            Qt::UniqueConnection));
      }

      // выполнить проверку в состоянии "Сканирование"
      QObject::connect(sScan, &FWState::entered,
                       m_usb, &FWUsbInstance:: slot_OnExecute,
                       Qt::ConnectionType(Qt::DirectConnection |
                                          Qt::UniqueConnection));

      // переход на "Пауза" после "Сканирование" по событию
      QObject::connect(Machine(),&FWStateMachine::sign_Paused,
                       m_usb,    &FWUsbInstance:: slot_OnPause,
                       Qt::DirectConnection);

      // возобновить работу после "Пауза" по команде события
      QObject::connect(Machine(),&FWStateMachine::sign_Resumed,
                       m_usb,    &FWUsbInstance:: slot_OnResume,
                       Qt::DirectConnection);

      /// @}
      return; // всё в порядке
    }
    QT_CATCH(QException& e)
    {
      e.what ();
    }

  // сканер не переместился в поток, или призошло исключение
  SetAutoDelete();        // уничтожить поток после exit()
  m_usb->deleteLater ();  // прибрать за собой
  m_usb = Q_NULLPTR;

  exit (EXIT_FAILURE);    // выход из рабочего потока
}


Не буду детально описывать всё – комментарии в наличии.
Остановлюсь на пару методах:
Метод FWUsbInstance:: slot_OnExecute() производит сканирование устройств.
void
FWUsbInstance::slot_OnExecute()
{
//  if(IsTimerMode())
//  {
    bool _tm = IsTiming ();
    if(_tm) StopTimer ();
    Reload ();
    if(_tm) StartTimer (10);
//  }
//  else
//    while(IsActive())
//      Reload ();
}

Здесь самое интересное – закомментированные операторы. Если убрать комментарии, а в методе FWUsbScanner::BeforeMachineStart() удалить ветку Machine()->AddBranch(sScan, sLoop), то всё будет работать, но условия цикла while() не достаточно для формирования точки выхода из потока. Так же невозможно обойтись одним методом Reload() в теле цикла.

Рассмотрим это подробнее.
Метод IsActive() проверяет флаг активности класса.
Слот slot_BreakProcess() базового класса FWItem сбрасывает флаг,
оператор
     QObject::connect(Machine(),&FWStateMachine::sign_Paused,
                       m_usb,    &FWUsbInstance:: slot_OnPause,
                       Qt::DirectConnection);

Слоты
void
FWUsbInstance::slot_OnPause()
{
  slot_BreakProcess();
}

И
void
FWUsbInstance::slot_OnResume()
{
  slot_ResumeProcess();
}

должны управлять приостановкой и возобновлением работы потока.
Всё должно отработать.
Но паузы не будет, цикл не завершиться.
В чём же дело?
Дело в том, что КА Machine() использует отправку сообщений методом postEvent(), т.е. помещает их в очередь отложенных сообщений. Слот slot_OnExecute() выполняется в рабочем потоке, том же, где и расположен конечный автомат.
Таким образом, все отложенные сообщения будут ожидать завершения метода slot_OnExecute(): цикл while() прочно подвесит задачу. Даже «оживление» очереди сообщений методом QCoreApplication::processEvents() не спасёт ситуацию!

Избавление от циклов рабочего потока, отчасти, и вынудило меня применить для управления конечный автомат.
Решением задачи послужило добавление нового состояния (sLoop) и перехода с возвратом к состоянию сканирования.
void
FWUsbInstance::slot_OnLoop()
{
  if(MachineExists())
    Machine()->PostEvent(FWEvent::ET_TASK,FWTaskEvent::TEI_Read);
}

Как рекомендация, могу посоветовать не «жадничать» на добавление состояний КА. Особенно, в тех местах программы, где меняют значения какие либо признаки. В конце концов, сама теория КА Мура обязывает создать отдельное состояние для уникального выходного значения. В данном случае, это – признак активности.

Вот, пожалуй, и всё, что я хотел рассказать.

Итог


  • Рассмотрены некоторые практические аспекты и тонкости реализации классов управления потоками на основе класса QThread.
  • Показан пример применения конечных автоматов Qt для управления работой потоков.
  • Создана технология «прозрачности» для классов наследников QThread при перегрузке методов QThread защищённого (protected) раздела класса.
  • Добавлены практические советы по использованию помощника класса QThread_helper для управления жизненным циклом потока.
  • Показан пример модификации графа КА без изменений основного алгоритма управления потоком.
  • Рассмотрены аспекты работы с контекстом библиотеки libUSB.
  • Показан пример создания классов наследников от QEvent, позволяющие гибко настраивать типы событий для собственных нужд.


Примечания


Целью статьи не ставилось предоставление полных исходных кодов для всех классов.
Мне хотелось донести только идею (технологию). Невозможно выложить собственную библиотеку в силу множества сопутствующих файлов. Прикреплённые листинги помогут понять саму суть, а это и являлось целью статьи.

У меня нет дара красивого изложения материала. Прошу простить за возможные «корявые» обороты.

Я не часто заглядываю в почтовый ящик – социально-информационный аутизм. :) Прошу простить, если ответ очень опоздает.

Дополнитеьные листинги



FWStateMachine.h

#ifndef FWSTATEMACHINE_H
#define FWSTATEMACHINE_H

#ifndef QSTATEMACHINE_DEBUG
#   define QSTATEMACHINE_DEBUG
#endif

#include "FWState.h"

FW_BEGIN_NAMESPACE


//------------------------------------------------------------------------------
///
/// @brief Класс FWStateMachine представляет объект "Конечный автомат".
/// @see QStateMachine
///
class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWStateMachine : public QStateMachine
{
    friend class FWThread;
    Q_OBJECT

    Q_CLASSINFO("brief",          "Finite-state machine Class")
    Q_CLASSINFO("created",        "03-JUN-2015")
    Q_CLASSINFO("modified",       "03-JUN-2015")
    //
    Q_CLASSINFO("project",        "Common Qt-based Framework")
    //
    Q_CLASSINFO("info_en",        "https://en.wikipedia.org/wiki/Finite-state_machine")
    Q_CLASSINFO("info_ru",        "https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BD%D0%B5%D1%87%D0%BD%D1%8B%D0%B9_%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82")

    /// @name Локальные типы класса.
    /// @{
    /// приведение типа базового класса для независимости реализации
    typedef QStateMachine BaseClass;
    /// @}

  //----------------------------------------------------------------------------
  private:
    ///
    /// @brief Атрибут m_final_signals представляет карту объектов как ключей
    /// с сигналами как значения, переводящими объект данного класса (конечный
    /// автомат) в состояние завершения работы.
    /// @value FWFinalSignalsMap::key() Ключ карты. Указатель на объект,
    /// инициирующий сигнал для завершения работы конечного автомата как
    /// экземпляра данного класса;
    /// @value FWFinalSignalsMap::value () Значение для ключа карты. Сигнал
    /// завершения работы конечного автомата как экземпляра данного класса,
    /// посылаемого объектом-ключом карты.
    ///
    QScopedPointer<FWState> m_state_group;

    ///
    /// @brief Объект m_history_state представляет средство возврата в предыдущее
    /// активное подсостояние для текущего состояния.
    /// Используется для  механизма "прерываний" текущего КА. Суть механизма в том,
    /// чтобы конечный автомат мог выполнить некоторую не связанную задачу,
    /// после чего конечный автомат должен продолжить делать то, что он делал
    /// раньше (т.е. вернутся в прежнее состояние, которое, в этом случае,
    /// является @sa m_state_main)
    ///
    /// @note Историческое состояние создаётся как дочернее состоянию, для
    /// которого мы хотим записать текущее дочернее состояние; когда автомат
    /// обнаруживает присутствие такого состояние во время
    /// выполнения, он автоматически записывает текущее (действительное) дочернее
    /// состояние, когда происходит выход из родительского состояния.
    /// Переход в историческое состояние - это, на самом деле, переход в
    /// дочернее состояние, в котором автомат был ранее сохранён;
    /// автомат, автоматически "направляет" переход к действительному дочернему
    /// состоянию. (\a http://doc.crossplatform.ru/qt/4.7.x/statemachine-api.html)
    ///
    QScopedPointer<QHistoryState> m_state_history;

    ///
    /// @brief Атрибут m_final_state представляет прикреплённый к экземпляру
    /// данного класса указатель на состояние завершения работы конечного
    /// автомата как экземпляра данного класса.
    ///
    QScopedPointer<QFinalState> m_state_final;

    ///
    /// @brief Атрибут m_state_error представляет прикреплённый к экземпляру
    /// данного класса указатель на состояние ошибки.
    ///
    QScopedPointer<FWState> m_state_error;

  //----------------------------------------------------------------------------
  public:

    explicit
    FWStateMachine(QObject* aParent = Q_NULLPTR);

    explicit
    FWStateMachine(QState::ChildMode  aChildMode,
                   QObject*           aParent = Q_NULLPTR);


    BaseClass*
    AsBaseClass() { return qobject_cast<BaseClass*>(this); }

    bool
    StateGroupExists () const { return !m_state_group.isNull (); }

    FWState*
    StateGroup () const
    { return StateGroupExists () ? m_state_group.data () : Q_NULLPTR; }

    inline FWState*
    StateIdle () const
    {
      return
          StateGroupExists () ?
            qobject_cast<FWState*>(StateGroup()->initialState ()):
            Q_NULLPTR;
    }

    inline bool
    StateHistoryExists () const { return !m_state_history.isNull (); }

    inline QHistoryState*
    StateHistory () const
    { return StateHistoryExists () ? m_state_history.data () : Q_NULLPTR; }

    inline bool
    StateFinalExists () const { return !m_state_final.isNull (); }

    inline QFinalState*
    StateFinal () const
    { return StateFinalExists () ? m_state_final.data () : Q_NULLPTR; }

    inline bool
    StateErrorExists () const { return !m_state_error.isNull (); }

    inline FWState*
    StateError () const
    { return StateErrorExists () ? m_state_error.data () : Q_NULLPTR; }

    ///
    /// @brief Метод IsValid сообщает о готовности экземпляра класса как
    /// конечного автомата к работе.
    /// @note Не имеет смысла запускать автомат на выполнение, если у него нет хотя
    /// бы одного условия завершения работы.
    /// @return Булево значение признака готовности к работе:
    /// @value TRUE автомат готов к работе;
    /// @value FALSE автомат не готов к работе: требуется назначение хотя бы
    /// одного условия завершения работы c помощью метода @sa SubscribeFinalSignal;
    /// @see SubscribeFinalSignal
    ///
    virtual bool
    IsValid () const { return NOT_NULL(initialState ()); }

    virtual bool
    IsActive () const { return isRunning (); }

    /**
     * @brief Метод PostEvent ставит событие класса @sa FWEvent в очередь
     * обработчика событияй КА.
     * @param aEvent Указатель на событие типа @sa FWEvent;
     * @param priority Приоритет обслуживания события.
     */
    inline void
    PostEvent(FWEvent* aEvent, EventPriority priority = NormalPriority)
    {
      if(NOT_NULL(aEvent))
        BaseClass::postEvent (aEvent->AsBaseClass (), priority) ;
    }

    /**
     * @brief Метод PostEvent ставит событие типа @sa FWEventType с о значением
     * определённого идентиыикатора в очеоедь обработчика событияй КА.
     * @param aEventType
     * @param aID
     * @param priority
     */
    inline void
    PostEvent(FWEvent::FWEventType aEventType, FWIdentificator aID,
              EventPriority priority = NormalPriority)
    {
      PostEvent (new FWEvent(aEventType,aID), priority) ;
    }

    /**
     * @brief Метод postDelayedEvent перелпределяет базовый
     * метод @sa QStateMachine::postDelayedEvent по постановке события
     * типа @sa FWEvent в очередь обработчика событияй КА c определённой
     * задержкой обслуживания.
     * @param aEvent Указатель на событие типа @sa FWEvent;
     * @param aDelay Значение задержки в милисекундах.
     * @return Идентификатор, ассоциированный с событием, или -1 в случае
     * невозможности отправки сообщения.
     */
    inline int
    PostDelayedEvent(FWEvent* aEvent, int aDelay)
    {
      if(NOT_NULL(aEvent))
        return BaseClass::postDelayedEvent (aEvent->AsBaseClass (), aDelay) ;
      return -1;
    }

    /**
     * @brief PostDelayedEvent
     * @param aEventType
     * @param aID
     * @param aDelay
     */
    inline void
    PostDelayedEvent(FWEvent::FWEventType aEventType, FWIdentificator aID,
              int aDelay)
    {
      PostDelayedEvent(new FWEvent(aEventType,aID), aDelay) ;
    }

    /**
     * @brief Метод Configuration дополняет базовый @sa QStateMachine::configuration
     * ограничением по типу @sa FWState результирующего списка состояний.
     * @return Множество состояний типа @sa FWState КА.
     */
    QSet<FWState*>
    Configuration() const;

    /// @name Группа методов добавления ветвей графа Конечного автомата
    /// @{

    /**
     * @brief AddBranch
     * @param aSource
     * @param aTarget
     * @return
     */
    virtual bool
    AddBranch ( QState* aSource, QAbstractState* aTarget )
    {
      if(NOT_NULL(aSource))
        return aSource->addTransition(aTarget);
      return false;
    }

    /**
     * @brief AddBranch
     * @param aEventType
     * @param aID
     * @param aSource
     * @param aTarget
     * @return
     */
    virtual bool
    AddBranch ( QEvent::Type    aEventType,
                FWIdentificator aID,
                QState* aSource, QAbstractState* aTarget = Q_NULLPTR );

    /**
     * @brief AddBranch
     * @param aEventType
     * @param aID
     * @param aSource
     * @param aTarget
     * @return
     */
    virtual bool
    AddBranch ( FWEvent::FWEventType  aEventType,
                FWIdentificator       aID,
                QState* aSource, QAbstractState* aTarget = Q_NULLPTR );

    /**
     * @brief AddBranch
     * @param aEvent
     * @param aSource
     * @param aTarget
     * @return
     */
    virtual bool
    AddBranch ( FWEvent* aEvent,
                QState* aSource, QAbstractState* aTarget = Q_NULLPTR );

    /// @}
  //----------------------------------------------------------------------------
  protected:

    ///
    /// @brief Метод Initialisation
    ///
    virtual void
    Initialisation ();

  //----------------------------------------------------------------------------
  Q_SIGNALS:

    ///
    /// @brief Сигнал sign_Paused оповещает о переходе КА в режим ожидания.
    ///
    void
    sign_Paused ();

    ///
    /// @brief Сигнал sign_Resumed оповещает о выходе КА из режим ожидания.
    /// @see QStateMachine::started, QStateMachine::stopped
    ///
    void
    sign_Resumed ();

  //----------------------------------------------------------------------------
  public Q_SLOTS:

    ///
    /// @brief Слот slot_Pause производит попытку перевода КА в режим ожидания.
    ///
    void
    slot_Pause ()
    { postEvent (new FWInternalEvent(FWInternalEvent::OEI_Pause)); }

    ///
    /// @brief Слот slot_Resume производит попытку вывода КА из режима ожидания.
    ///
    void
    slot_Resume ()
    { postEvent (new FWInternalEvent(FWInternalEvent::OEI_Resume)); }

  //----------------------------------------------------------------------------
  protected Q_SLOTS:

    ///
    /// @brief slot_OnLoging
    ///
    virtual void
    slot_OnLoging ()
    { qDebug() << qPrintable(Q_FUNC_INFO); }

    ///
    /// @brief slot_OnError
    ///
    virtual void
    slot_OnError ()
    { qDebug() << qPrintable(Q_FUNC_INFO); }

    ///
    /// @brief slot_OnPaused
    ///
    virtual void
    slot_OnPaused ()
    {
      qDebug() << qPrintable(Q_FUNC_INFO);
      if(thread ())
        thread ()->yieldCurrentThread ();
      emit sign_Paused();
    }

    ///
    /// @brief slot_OnResume
    ///
    virtual void
    slot_OnResume ()
    {
      qDebug() << qPrintable(Q_FUNC_INFO);
      emit sign_Resumed();
    }

    ///
    /// @brief slot_OnStop
    ///
    virtual void
    slot_OnStop ()
    {
      qDebug() << qPrintable(Q_FUNC_INFO);
    }

    ///
    /// @brief slot_OnDefaultHistoryChanged
    ///
    virtual void
    slot_OnDefaultHistoryChanged()
    {
      if(StateHistoryExists ())
        if(NOT_NULL(StateHistory()->defaultState()))
          qDebug() << "-H-" << StateHistory()->defaultState()->objectName();
    }
};
//------------------------------------------------------------------------------

FW_END_NAMESPACE

#endif // FWSTATEMACHINE_H


FWStateMachine.cpp

#include "FWStateMachine.h"
#include "FWTransition.h"
#include <QDebug>

FW_USING_NAMESPACE

//------------------------------------------------------------------------------
FWStateMachine::FWStateMachine(QObject* aParent)
  : BaseClass       (aParent)
  , m_state_group   (Q_NULLPTR)
  , m_state_history (Q_NULLPTR)
  , m_state_final   (Q_NULLPTR)
  , m_state_error   (Q_NULLPTR)
{
  Initialisation ();
}

//------------------------------------------------------------------------------
FWStateMachine::FWStateMachine(QState::ChildMode  aChildMode,
                               QObject*           aParent)
  : FWStateMachine(aParent)
{
  BaseClass::setChildMode (aChildMode);
}

//------------------------------------------------------------------------------
QSet<FWState*>
FWStateMachine::Configuration() const
{
  QSet<FWState*> _retval;

  QSet<QAbstractState*> _as =  BaseClass::configuration ();
  Q_FOREACH(QAbstractState* _s, _as)
    _retval.operator += (qobject_cast<FWState*>(_s));
  return  _retval;
}

bool
FWStateMachine::AddBranch(QEvent::Type aEventType, FWIdentificator aID,
                          QState* aSource, QAbstractState* aTarget)
{
  if(NOT_NULL(aSource))
    if(aSource->inherits(FW_CLASS_STR(FWState).toLatin1 ()))
      return qobject_cast<FWState*>(aSource)->addTransition(aEventType, aID,
                                                           aTarget);
  return false;
}

bool FWStateMachine::AddBranch(FWEvent::FWEventType aEventType,
                               FWIdentificator aID,
                               QState* aSource, QAbstractState* aTarget)
{
  if(NOT_NULL(aSource))
  {
    if(aSource->inherits(FW_CLASS_STR(FWState).toLatin1 ()))
      return qobject_cast<FWState*>(aSource)->addTransition(aEventType, aID,
                                                           aTarget);
    else
    {
      QEventTransition* t = new QEventTransition(
                              (QObject*)aID,
                              static_cast<QEvent::Type>(aEventType), aSource);
      if(NOT_NULL(aTarget))
        t->setTargetState(aTarget);
      return true;
    }
  }
  return false;
}

bool
FWStateMachine::AddBranch(FWEvent* aEvent, QState* aSource, QAbstractState* aTarget)
{
  if(NOT_NULL(aSource))
  {
    if(aSource->inherits(FW_CLASS_STR(FWState).toLatin1 ()))
      return qobject_cast<FWState*>(aSource)->addTransition(aEvent, aTarget);
    else
    {
      QEventTransition* t = new QEventTransition(
                              (QObject*)(aEvent->Identificator()),
                              aEvent->type(), aSource);
      if(NOT_NULL(aTarget))
        t->setTargetState(aTarget);
      return true;
    }
  }
  return false;
}

//------------------------------------------------------------------------------
void
FWStateMachine::Initialisation()
{
  bool v_active = IsActive ();
  if(v_active)
    stop ();

  /// @name Определение группы состояний КА
  /// @{
  if(!StateGroupExists ())
    m_state_group.reset (new FWState(this));

  FWState* sIdle = new FWState(StateGroup ());
  StateGroup ()->setInitialState (sIdle);
  /// @}

  /// @name Назначение правил работы исторического состояния КА
  /// @{
  if(!StateHistoryExists ())
    m_state_history.reset (new QHistoryState(QHistoryState::DeepHistory,StateGroup ()));

  /// переход с историческим состоянием, в качестве цели, закончится в наиболее
  /// глубоко вложенном состоянии-потомке @sa m_state_group, в котором родительское
  /// находилось в последний раз, когда из него вышли.
  StateHistory ()->setHistoryType (QHistoryState::DeepHistory);

  /// @}

  /// @name Определение состояниz завершения работы КА
  /// @{
  if(!StateFinalExists ())
    m_state_final.reset (new QFinalState(this));
  StateFinal()->setObjectName("State FINAL");
  /// @}

  /// @name Определение ошибочного состояния КА
  /// @{
  if(!StateErrorExists ())
    m_state_error.reset (new FWState(this));

  /// @}

  /// @name Назначение переходов для основного состояния КА
  /// @{

  AddBranch(FWEvent::ET_INTERNAL,FWInternalEvent::OEI_Stop,
            StateGroup (), StateFinal ());
  AddBranch(FWEvent::ET_INTERNAL,FWInternalEvent::OEI_Error,
            StateGroup (), StateError ());

  /// @}

  /// @name Назначение переходов для состояния приостановки работы
  /// @{

  FWState* sPause = new FWState(this);
  AddBranch(FWEvent::ET_INTERNAL,FWInternalEvent::OEI_Pause,
            StateGroup (), sPause);
  AddBranch(FWEvent::ET_INTERNAL,FWInternalEvent::OEI_Resume,
            sPause, StateHistory ());

  /// @}

  /// @name Назначение переходов для состояния ошибки
  /// @{
  AddBranch(StateError (), StateFinal ());
  /// @}

  /// @name Назначение обработчиков переходов между состояниями КА
  /// @{

  connect( sPause,&FWState::        entered,
           this,  &FWStateMachine:: slot_OnPaused,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  connect( sPause,&FWState::        exited,
           this,  &FWStateMachine:: slot_OnResume,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  connect( StateFinal (),&QFinalState::    entered,
           this,         &FWStateMachine:: slot_OnStop,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  connect( StateError (),&FWErrorState::   entered,
           this,         &FWStateMachine:: slot_OnError,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  connect( StateHistory(),&QHistoryState::defaultStateChanged,
           this,          &FWStateMachine::slot_OnDefaultHistoryChanged,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  /// @}

  if(NOT_EQUAL(initialState (),StateGroup ()))
    setInitialState (StateGroup ());

  StateGroup ()->   setObjectName("State GROUP");
  StateHistory ()-> setObjectName("State HISTORY");
  StateError()->    setObjectName("State ERROR");
  StateFinal()->    setObjectName("State FINAL");
  sPause->          setObjectName("State PAUSE");
  sIdle->           setObjectName("State IDLE");

  if(v_active) start ();
}

//------------------------------------------------------------------------------


FWTransition.h

#ifndef FW_TRANSITION_H
#define FW_TRANSITION_H

#include <FWEvent>
#include <QSignalTransition>

FW_BEGIN_NAMESPACE

//------------------------------------------------------------------------------
class FWState;
class FWStateMachine;

//------------------------------------------------------------------------------
///
/// @brief Класс FWTransition описывает переход между состояниями типа @sa FWState
/// конечного автомата типа @sa FWStateMachine.
/// Класс создан наследованием @sa QEventTransition.
///
class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWTransition : public QAbstractTransition
{
    Q_OBJECT

    Q_CLASSINFO("brief",          "Exlusive  Finite-state Class")
    Q_CLASSINFO("created",        "03-JUN-2015")
    Q_CLASSINFO("modified",       "24-JUN-2015")
    //
    Q_CLASSINFO("project",        "Common Qt-based Framework")

    Q_PROPERTY(FWIdentificator  eventID
               READ             EventID
               WRITE            SetEventID
               NOTIFY           sign_EventIDChanged)
    Q_PROPERTY(QEvent::Type     eventType
               READ             EventType
               WRITE            SetEventType
               NOTIFY           sign_EventTypeChanged)

    //----------------------------------------------------------------------------
    /// @name Локальные типы класса.
    /// @{
    /// приведение типа базового класса для независимости реализации
    /// @see QEventTransition
    typedef QAbstractTransition BaseClass;
    /// @}

    Q_ENUMS(TransitionState)

  //----------------------------------------------------------------------------
  public:

    /**
     * @brief Перечисление TransitionState содержит типы состояний перехода.
     */
    enum TransitionState
    {
      UndefinedState, ///< неопределённое состояние перехода
      SignalState,    ///< переход вызывется сигналом объекта
      EventState,     ///< переход вызывется наступлением события
      ErrorState      ///< переход содержит ошибки
    };

  //----------------------------------------------------------------------------
  private:

    /**
     * @brief Атрибут m_event содержит эталонный экземпляр события
     * обрабатываемого типа.
     *
     * @see FWIdentificator, QEvent, QEvent::Type.
     */
    QEvent::Type    m_event_type;

    /**
     * @brief Атрибут m_event_id cодержит идентификатор события наследника
     * класса @sa FWEvent. В случае поступления событи типа @sa FWSignalEvent,
     * атрибут содержит ссылку на объект отправитель сигнала, приведённй к
     * типу @sa FWIdentificator выражением FWIdentificator(<QObject*>).
     *
     * @see FWTransition::Object, FWTransition::ObjectExists.
     */
    FWIdentificator m_event_id;

    /**
     * @brief Атрибут m_state описывает состояние перехода
     * @see TransitionState
     */
    TransitionState m_state;

  //----------------------------------------------------------------------------
  public:

    /**
     * @brief Конструктор FWTransition перегружает базовый конструктор
     * @sa QAbstractTransition::QAbstractTransition.
     * @param aSourceState Указатель на исходное состояние конечного автомата.
     */
    explicit
    FWTransition(FWState*  aSourceState = Q_NULLPTR);

    /**
     * @brief Конструктор FWTransition создаёт переход по наступлению
     * определённого события типа @sa FWEvent::FWEvent
     * @param aEvent Указатель на событие, вызывающего переход;
     * @param aSourceState Указатель на исходное состояние конечного автомата.
     *
     * "see FWIdentificator, FWEvent, FWState.
     */
    explicit
    FWTransition(QEvent*  aEvent,
                 FWState* aSourceState = Q_NULLPTR);

    /**
     * @brief Конструктор FWTransition создаёт переход по наступлению
     * определённого события типа @sa QEvent::Type.
     * @param aEventType Тип события, вызывающего переход;
     * @param aID Идентификатор события, вызывающего переход;
     * @param aSourceState указатель на исходное состояние конечного автомата.
     *
     * @see QEvent, FWEvent, FWState, QState.
     */
    explicit
    FWTransition(QEvent::Type     aEventType,
                 FWIdentificator  aID,
                 FWState*         aSourceState = Q_NULLPTR);

    /**
     * @brief Конструктор FWTransition создаёт переход по наступлению
     * определённого события типа @sa FWEvent::FWEventType.
     * @param aEventType Тип события, вызывающего переход;
     * @param aID Идентификатор события, вызывающего переход;
     * @param aSourceState указатель на исходное состояние конечного автомата.
     *
     * @see QEvent, FWEvent, FWState, QState.
     */
    explicit
    FWTransition(FWEvent::FWEventType aEventType,
                 FWIdentificator      aID,
                 FWState*             aSourceState = Q_NULLPTR)
      : FWTransition(static_cast<QEvent::Type>(aEventType), aID, aSourceState)
    {}

    /**
     * @brief Конструктор FWTransition создаёт переход по наступлению
     * служебного события (внутреннего для конечного автомата) типа
     * @sa FWEvent::ET_INTERNAL.
     * @param aID Идентификатор события, вызывающего переход;
     * @param aSourceState указатель на исходное состояние конечного автомата.
     *
     * @see QEvent, FWEvent, FWState, QState.
     */
    explicit
    FWTransition(FWInternalEvent::FWInternalIdentificators aID,
                 FWState*         aSourceState = Q_NULLPTR)
      : FWTransition(static_cast<QEvent::Type>(FWEvent::ET_INTERNAL),aID,
                     aSourceState)
    {}

//    explicit
//    FWTransition(QObject*         aSender,
//                 const char*      aSignalName,
//                 QObject*         aRessiver,
//                 const char*      aSlotName,
//                 FWState*         aSourceState = Q_NULLPTR);

    /**
     * @brief Метод AsBaseClass преобразует объект класса к типу базового класса.
     * @return Указатель на объект класса преобразованный к типу базового класса.
     */
    inline BaseClass*
    AsBaseClass () { return qobject_cast<BaseClass*>(this); }

    /**
     * @brief Метод EventType возвращает тип события как @sa QEvent::Type.
     * @return Значение типа события.
     */
    inline QEvent::Type
    EventType () const { return m_event_type; }

    /**
     * @brief Метод SetEventType устанавливает новое значение типа события.
     * @param aType Новое значение типа события.
     */
    inline void
    SetEventType (const QEvent::Type aType)
    {
      if(NOT_EQUAL(EventType (),aType))
      {
        m_event_type = aType;
        emit sign_EventTypeChanged (EventType ());
      }
    }

    /**
     * @brief Метод Identificator отображает идентификатор события, связанного
     * с данным переходом.
     * @return Значение идентификатора типа @sa FWIdentificator.
     */
    inline FWIdentificator
    EventID () const { return m_event_id; }

    /**
     * @brief Метод setEventID устанавливает значение атрибута @sa m_id в
     * значение параметра.
     * @param aID Новое значение идентификатора события для текущего перехода.
     */
    inline void
    SetEventID (const FWIdentificator aID)
    {
      if(NOT_EQUAL(EventID (),aID))
      {
        m_event_id = aID;
        emit sign_EventIDChanged (EventID ());
      }
    }

    FWStateMachine*
    Machine () const;

    inline bool
    operator == (const FWTransition& aOther)
    {
      return
          EQUAL(EventType   (), aOther.EventType  ()) &&
          EQUAL(EventID     (), aOther.EventID    ()) &&
          EQUAL(sourceState (), aOther.sourceState()) &&
          EQUAL(targetState (), aOther.targetState());
    }

  //----------------------------------------------------------------------------
  protected:

    /**
     * @brief Метод SetState устанавливает новое состояние для объекта класса.
     * @param aState Значение для нового состояния для объекта класса
     * @sa TransitionState
     */
    virtual void
    SetState (TransitionState aState)
    {
      if(m_state != aState)
        m_state = aState;
    }

    /**
     * @brief  Метод FWTransition::eventTest перегружает базовый метод
     * @sa QEventTransition::eventTest для специфичной проверки допустимости
     * срабатывания перехода по приходу события.
     * @param aEvent приходящее из вне событие.
     * @return Признак сравбатывания перехода:
     * @value TRUE событие принято, переход возможен;
     * @value FALSE событие отвергнуто, переход не возможен.
     *
     * Переход всегда связан с каким-либо КА. Поэтому, переход срабатывает по
     * событию для КА, определённого в теле события класса @sa FWEvent.
     * Условия проверки: обработать событие, если оно "обвёрнуто"
     * @sa StateMachine::WrappedEvent или @sa StateMachine::SignalEvent;
     *
     * @see QEvent, QEvent::Type.
     * @see QStateMachine, QStateMachine::WrappedEventб QStateMachine::SignalEvent.
     * @see FWEvent, FWEvent::Machine
     */
    bool
    eventTest(QEvent* aEvent) Q_DECL_OVERRIDE;

    /**
     * @brief Метод onTransition
     * @param aEvent
     */
    void
    onTransition(QEvent* aEvent) Q_DECL_OVERRIDE;

  //----------------------------------------------------------------------------
  Q_SIGNALS:

    /**
     * @brief sign_EventTypeChanged
     * @param aType
     */
    void
    sign_EventTypeChanged (const QEvent::Type aType);

    /**
     * @brief Сигнал sign_eventIDChanged оповещает о смене значения
     * привязанного к переходу идентификатора @sa m_id.
     * @param aID Новое значение идентификатора @sa m_id.
     *
     * @see FWIdentificator
     */
    void
    sign_EventIDChanged (const FWIdentificator aID);

    ///
    /// @brief Сигнал sign_Transiting сообщает о выполнении перехода
    /// @param aSource Указатель на объект-состояние, из которого выполняется
    /// переход;
    /// @param aTarget Указатель на целевое объект-состояние перехода;
    /// @param aTransition Указатель на объект класса @sa FWTransition, который
    /// стал источником перехода.
    ///
    void
    sign_Transiting (QState* aSource, QAbstractState* aTarget,
                     QAbstractTransition* aTransition);
};
//------------------------------------------------------------------------------

FW_END_NAMESPACE

#endif // FWTRANSITION_H


FWTransition.cpp

#include "FWTransition.h"
#include "FWState.h"
#include "FWStateMachine.h"

FW_USING_NAMESPACE

//------------------------------------------------------------------------------
FWTransition::FWTransition(FWState* aSourceState)
  : BaseClass     (aSourceState)
{
  SetState(UndefinedState);
  SetEventID (EMPTY_ID);
  SetEventType (static_cast<QEvent::Type>(QEvent::None));
}

//------------------------------------------------------------------------------
FWTransition::FWTransition(QEvent* aEvent, FWState* aSourceState)
  : FWTransition(aSourceState)
{
  if(NOT_NULL(aEvent))
  {
    if(!FWEvent::IsSystem (aEvent))
    {
      SetState(EventState);
      SetEventID (static_cast<FWEvent*>(aEvent)->Identificator ());
      SetEventType (aEvent->type ());
    }
  }
  else
    SetState(ErrorState);
}

//------------------------------------------------------------------------------
FWTransition::FWTransition(QEvent::Type     aEventType,
                           FWIdentificator  aID,
                           FWState*         aSourceState)
  : FWTransition(aSourceState)
{
  SetState    (EventState);
  SetEventID  (aID);
  SetEventType(aEventType);
}

//------------------------------------------------------------------------------
//FWTransition::FWTransition(QObject*     aSender,
//                           const char*  aSignalName,
//                           QObject*     aRessiver,
//                           const char*  aSlotName,
//                           FWState*     aSourceState)
//  : FWTransition  (aSourceState)
//{
//  SetState(SignalState);
//  SetEventID (FWIdentificator (aSender));
//  SetEventType (static_cast<QEvent::Type>(FWEvent::ET_GLOBAL));

//  if(NOT_NULL(aSender) && NOT_NULL(aRessiver))
//  {
//    int v_offset = *aSignalName == '0'+QSIGNAL_CODE ? 1 : 0;
//    int v_signal_index =
//        aSender->metaObject ()->indexOfSignal (
//          QMetaObject::normalizedSignature (aSignalName+v_offset));

//    v_offset = *aSlotName == '0'+QSLOT_CODE ? 1 : 0;
//    int v_slot_idx =
//        aRessiver->metaObject ()->indexOfSlot (
//          QMetaObject::normalizedSignature (aSlotName+v_offset));

//    if(QMetaObject::connect (aSender, v_signal_index,
//                             aRessiver, v_slot_idx,
//                             Qt::DirectConnection))
//    {
//      aRessiver->setParent (Q_NULLPTR);
//      aRessiver->moveToThread (thread ());
//    }
//    else
//      SetState(ErrorState);
//  }
//}

//------------------------------------------------------------------------------
FWStateMachine*
FWTransition::Machine() const
{
  return qobject_cast<FWStateMachine*>(machine ());
}

//------------------------------------------------------------------------------
bool
FWTransition::eventTest(QEvent* aEvent)
{
  bool v_retval = FWEvent::IsValid (aEvent);

  if(v_retval)
    v_retval = EQUAL(aEvent->type (),EventType ());

  if(v_retval)
  {
      FWEvent* v_e = static_cast<FWEvent*>(aEvent);
      v_retval =
          EQUAL(v_e->type (),         EventType ()) &&
          EQUAL(v_e->Identificator (),EventID   ());
  }

  return v_retval;
}

//------------------------------------------------------------------------------
void
FWTransition::onTransition(QEvent* aEvent)
{
  aEvent->accept();
//  qDebug() << "-T- " +
//              sourceState ()->objectName() +
//              " -->> " +
//              targetState()->objectName();
  emit sign_Transiting (sourceState (),targetState (), this->AsBaseClass());
}

//------------------------------------------------------------------------------




Буду рад, если моя заметка поможет вам решить задачи или найти выход из … потоко-затруднительного положения.

Спасибо.

Комментарии (18)


  1. Riateche
    07.07.2015 19:19
    +2

    В статье затронуто очень много разных вопросов — и потоки в Qt, и конечные автоматы, и libusb. Причем связаны между собой они очень слабо. Если бы у меня была подобная задача, я бы предпочел найти отдельную обстоятельную статью по каждому вопросу, а не одну, где всё вместе. Поэтому не очень понятно предназначение статьи.

    Переопределив метод QThread::run(), знайте, что все объекты, созданные в нем, будут недоступны из вне, т.к. созданы в стеке метода run() (хотя и это можно обойти).
    Поясните эту фразу. Во-первых, если объект создан через new, он будет помещен в heap, а не стек. Во-вторых, если объект создан на стеке, он будет на том же самом стеке независимо от того, переопределили вы QThread::run() или используете moveToThread. В-третьих, независимо от того, где создан объект, указатель на него можно переместить в любой другой поток (будет ли это безопасно — другой вопрос). Непонятно, о каком «из вне» идет речь.

    Теперь про завершение потоков. Если в потоке произойдет segfault или событие аналогичной катастрофичности, то упадет весь процесс целиком, и сделать тут мало что можно. А чтобы справиться с неотловленными исключениями, необязательно использовать обертку для QThread. Достаточно в QThread::run сделать безусловный try/catch. Точка выхода из потока тут тоже одна — это завершение метода QThread::run. После этого Qt самостоятельно завершит поток.

    И на всякий случай замечу, что правильно zero, а не zerro.


    1. coffeesmoke Автор
      07.07.2015 20:00

      Спасибо за обстоятельный комментарий.
      По первому абзацу отвечу, что цель статьи — показать возможность применения конечных автоматов для управления жизненным циклом потока.
      Важна была идея. Возможно, кто-то увидел для себя решение.
      Например, Вы комментарием подсказали читателям направление более основательно изучения всех трёх составляющих, и это — здорово!
      Работу с libusb выбрал как пример, на котором проверил материал.
      Так же эту технологи. использую для себя при сканировании каталогов на обновление файловой системы, применяю при работе с подключениями к QDatabase.

      С фразой получилось «корявенько». Для объектов стека метода run() не имеет смысла применение moveToThread(), т.к. это — переменная метода (возможно, я чего, то не понял).
      Указатели нельзя физически (если, конечно, не применить memmove()) переместить в другой поток: память у потоков общая, да и не имеет смысла. Вроде, это видно из исходников QT. Речь идёт о смене владельца указателя.

      По третьему пункту: Thread_helper позволяет удалить объект потока простым delete вызвав дестуктор объекта потока и остановив конечный автомат. В коде деструктора класса потока есть quit() и т.д.

      Спасибо за поправку к исключениям. Буду думать…


      1. Riateche
        07.07.2015 20:30

        Для объектов стека метода run() не имеет смысла применение moveToThread(), т.к. это — переменная метода (возможно, я чего, то не понял).
        Для объектов, созданных в некотором потоке, не имеет смысл вызывать moveToThread, потому что они по умолчанию уже принадлежат этому потоку. Кроме того, если вы переопределяете QThread::run и не запускаете event loop (а в реализации по умолчанию он запускается), то moveToThread теряет смысл, потому что единственное, на что он влияет, — в каком потоке будут вызываться обработчики событий и слоты объекта, а при отсутствии event loop в потоке они вызываться не будут вообще.

        Указатели нельзя физически (если, конечно, не применить memmove()) переместить в другой поток: память у потоков общая, да и не имеет смысла. Вроде, это видно из исходников QT. Речь идёт о смене владельца указателя.
        Указатель — это просто число, и его легко скопировать в другой поток. Затем можно использовать этот объект одновременно или по очереди в двух потоках. И это в общем случае не зависит от того, где и как объект создан.

        Понятие владельца весьма расплывчато. Parent object в Qt — это другой объект, который удалит наш объект вместе с собой. Thread affinity, как я уже сказал, определяет, в каком потоке обрабатываются события объекта. И ни один из этих параметров не запрещает использовать любой объект в любом другом потоке в то же время. Правда, для безопасного использования желательно, чтобы объект был thread-safe и его никто не удалил в процессе обработки.


        1. coffeesmoke Автор
          07.07.2015 21:35

          Для объектов, созданных в некотором потоке, не имеет смысл вызывать moveToThread, потому что они по умолчанию уже принадлежат этому потоку. Кроме того, если вы переопределяете QThread::run и не запускаете event loop (а в реализации по умолчанию он запускается), то moveToThread теряет смысл, потому что единственное, на что он влияет, — в каком потоке будут вызываться обработчики событий и слоты объекта, а при отсутствии event loop в потоке они вызываться не будут вообще.

          Вроде, это и так понятно… Всё таки, плохо я излагаю мысли. Слава богу, что пишу редко :).

          Основная идея всей этой статьи — показать возможную удобную альтернативу безблокировочному доступу к общим ресурсам для потоков.
          Буду материал дорабатывать дальше.
          Спасибо, что обратили внимание на тонкости разработки потоков. Копну глубже.


      1. AlexPublic
        08.07.2015 08:53
        +1

        Идея действительно хорошая. Правда она совсем не нова, но от повтора хуже не становится. )

        А вот для реализации подобного совершенно не нужны такие тяжеловесные вещи как Qt. Вполне достаточно поддержки потоков реализованных в стандартной библиотеке языка (std::thread и т.п.). Ну а в качестве конечного автомата, как уже было указано, вполне логично взять boost.msm.


        1. coffeesmoke Автор
          09.07.2015 10:29
          -1

          Я обращался статьёй к Qt-эшникам. В самом начале упомянул о boost. К тому же, есть у меня собственные наработки на ANSI C с использованием pthread функций. Вариантов — куча, умных и талантливых людей — и подавно. Статься создана для одной веви разработок — Qt Framework. Хотелось донести идею: вдруг кто-то найдёт для себя какое-то новое решение.


          1. AlexPublic
            09.07.2015 20:00

            Дело не в этом. Просто Qt — это очень тяжёлый фреймворк с довольно устаревшей архитектурой. Так что его имеет смысл использовать, только если нет другой альтернативы. Такая область есть и довольно широкая — написание кроссплатформенного GUI (собственно Qt практически единственная в мире библиотека, обеспечивающая написание GUI на все значимые платформы из одной кодовой базы). Так что приходится частенько использовать этого монстра. Однако в вашем случае не видно не то что требования на полную кроссплатформенность GUI, но даже вообще требования на наличие GUI. Соответственно если таких ограничений нет, то можно написать на порядок более изящную (используя современный C++) и лёгкую (сколько мегабайт занимает ваш примерчик и сколько времени он собирается?) программу.


            1. coffeesmoke Автор
              09.07.2015 20:17

              Требования кросcплатформенности есть, но я не могу тащить всю свою библиотеку (libFWGUI, libFWDtatbase, libFWCore...)! :)
              Повторю, статья для тех, кто работает, именно, с Qt.


              1. AlexPublic
                09.07.2015 21:04

                Так требуется просто кроссплатформенность (это как бы по умолчанию у C++ и boost'a) или кроссплатформенность GUI? В вашей статье я вообще не нашёл упоминания, что данная программка реализует GUI… )))

                Ну и даже если говорить о кроссплатформенном GUI, то надо точно определиться с набором требуемых плаформ, Потому как достаточно исключить из полного списка актуальных ОС (Windows, Android, iOS, OSX, Linux) хотя бы одну (скажем Android), как возникнет множество альтернатив. На порядок более лёгких и современных. Причём при реализации через обычные потоки и boost обсуждаемый код (многопоточная работа с usb с помощью конечного автомата) не будет зависть от выбора GUI библиотеки. )

                Т.е. лично моя позиция — даже если нам приходится использовать Qt для GUI, то только для этого его и надо применять, а всё остальное реализовывать независимыми средствами.


                1. coffeesmoke Автор
                  10.07.2015 09:03
                  -1

                  Было бы неплохо, если бы вы об этом создали статью. Я бы добавил в «Избранное». Видел стройное решение по конечным автоматам на google-разработка на boost.
                  Кстати, в сети очень мало и разрозненно упоминается работа с boost применительно к каким-либо технологиям. Для начинающих было бы очень полезно!


                1. coffeesmoke Автор
                  10.07.2015 09:12

                  Т.е. лично моя позиция — даже если нам приходится использовать Qt для GUI, то только для этого его и надо применять, а всё остальное реализовывать независимыми средствами.

                  Таким образом, вам придётся набирать несколько сотрудников (вместо одного Qt-эшника) для реализации проекта. Современные IT-технологии позволяют «расслабиться» в плане ресурсов и скорости. Это — «минус». «Плюс» — единая платформа разработки и снижение себестоимости проекта.
                  Это — так, размышления. :)


    1. coffeesmoke Автор
      07.07.2015 20:09

      Кстати, не применить ли вам для своей темы конечные автоматы и потки, скажем, для сканирования каталогов? Автомат отрабатывает эвенты, это может здорово пригодиться.


  1. monah_tuk
    09.07.2015 09:54

    С libusb много шишек набили, особенно при работе через USB 3.0, особенно на Windows. Если есть возможность заменить работу через какой-то стандартный класс, типа HID (использовать можно через libhid), то лучше использовать его. Либо писать драйвер. Скорее всего косяки в связке WinUSB и libusb. В случае HID будет использоваться драйвер HID.


    1. coffeesmoke Автор
      09.07.2015 10:23

      Без сомнения. К тому же, напрямую работать с драйвером *.sys, зачастую, невозможно, Приходится пользоваться zadig для конвертации.
      Однако, многие применяют libusb (исходники библиотеки можно поправить), В статье отражён пример. Плюс, хотелось обратить внимание на работу с контекстом libusb.


      1. monah_tuk
        09.07.2015 13:47

        исходники библиотеки можно поправить

        согласен, но не всегда всё тривиально, тем более на низком уровне, особенно в Windows.

        Плюс, хотелось обратить внимание на работу с контекстом libusb.

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


    1. coffeesmoke Автор
      09.07.2015 10:31

      WinUSB и libusb
      = zadig


      1. monah_tuk
        09.07.2015 11:27

        Да, задиг — по сути демо для libwdi, пришлось использовать для программы, что бы автоматом устанавливать «драйвер». Хотя есть возможность в устройстве прописать метки WCID, что мы и сделали, но… Или какой-то косяк, или что-то ещё: драйвер ставится, но устройство не отображает своего имени (чего не происходит при установке через Zadig/libwdi) и обратиться через libusb к нему нельзя. Выход: или вручную переставлять драйвер или форсировать через libwdi из своей приложухи (мы сделали второй вариант).

        Потом, на USB 3.0 на Win8 (по крайней мере), устройство отображается ДВА раза (у libusb). Первое всегда недоступно (а к нему и происходит обращение при попытке открыть через vid/pid), а второе доступно. Почему так — хз. Плюс косяки с некоторыми конроллерами — устройство видится, но любое обращение к нему, кроме запроса Device descriptor — фейл. Причём часто лечится перетыканием в другой порт того-же контроллера (но не всегда, есть «мёртвые» для libusb контроллеры). При этом, практически не припомню косяков при работе в USB 2.0 моде. Т.е. берём проводок USB 2.0 и втыкаем в тот же порт — всё магическим образом становится прекрасно.


        1. coffeesmoke Автор
          09.07.2015 11:44

          Да. Думаю разделить в дальнейшем релизы доступа для USB на usb_windows.h и usb_unix.h