2016-11-01 3 views
1

Недавно я начал использовать инфраструктуру QT. Вчера я начал программировать простое многопоточное приложение. На данный момент я несколько застрял в следующей проблеме.Цикл заблокированных событий Qt

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

class FooWorker : public QObject 
{ 
    Q_OBJECT 

public: 
    FooWorker() : QObject() { } 
    ~FooWorker() { } 

signals: 
    void notify(int); 
    void aborted(); 

public slots: 
    void doWork() 
    { 
     int counter = 0; 
     forever { 

      // For the sake of this example this reassembles a heavy computational process 
      if(counter++ < 10) { 
       emit notify(counter); 
       QThread::sleep(1); 
      } else { 
       counter = 0; 

       // Wait until we get a signal to restart the process 
       mutex_.lock(); 
       condition_.wait(&mutex_); 
       mutex_.unlock(); 
      } 
      // We should check for a cancellation flag every iteration... 
     } 

     emit aborted(); 
    } 

private: 
    QMutex mutex_; 
    QWaitCondition condition_; 
}; 

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

Второй класс, BarWorker, выглядит следующим образом:

class BarWorker : public QObject 
{ 
    Q_OBJECT 

public: 
    BarWorker() : QObject() { } 
    ~BarWorker() { } 

signals: 
    void aborted(); 

public slots: 
    void doWork() 
    { 
     forever { 
      // Another heavy computational process 
      QThread::sleep(1); 

      // We should check for a cancellation flag every iteration... 
     } 

     emit aborted(); 
    } 

    void onNotify(int value) 
    { 
     qDebug() << "Notification value:" << value; 
    } 
}; 

Опять слот «DoWork» будет планируется запустить в другом потоке. Слот будет работать вечно, чтобы выполнить тяжелый вычислительный процесс. Опять же, как только процесс будет выполнен, мы подождем, пока он не проснется снова (ради этого примера я оставил это в этом классе).

Наконец главный выглядит следующим образом:

int main(int argc, char *argv[]) 
{ 
    QCoreApplication a(argc, argv); 

    QThread* barThread = new QThread(); 
    BarWorker* barWorker = new BarWorker(); 
    barWorker->moveToThread(barThread); 

    QThread* fooThread = new QThread(); 
    FooWorker* fooWorker = new FooWorker(); 
    fooWorker->moveToThread(fooThread); 

    // Automatically deletes worker and thread 
    QObject::connect(fooThread, SIGNAL(started()), fooWorker, SLOT(doWork())); 
    QObject::connect(fooWorker, SIGNAL(aborted()), fooThread, SLOT(quit())); 
    QObject::connect(fooWorker, SIGNAL(aborted()), fooWorker, SLOT(deleteLater())); 
    QObject::connect(fooThread, SIGNAL(finished()), fooThread, SLOT(deleteLater())); 

    QObject::connect(barThread, SIGNAL(started()), barWorker, SLOT(doWork())); 
    QObject::connect(barWorker, SIGNAL(aborted()), barThread, SLOT(quit())); 
    QObject::connect(barWorker, SIGNAL(aborted()), barWorker, SLOT(deleteLater())); 
    QObject::connect(barThread, SIGNAL(finished()), barThread, SLOT(deleteLater())); 

    QObject::connect(fooWorker, SIGNAL(notify(int)), barWorker, SLOT(onNotify(int)), Qt::QueuedConnection); 

    fooThread->start(); 
    barThread->start(); 

    return a.exec(); 
} 

Когда я не запускать ничего приложения печатается. Этого следовало ожидать, потому что цикл событий экземпляра BarWorker заблокирован. По мере того как излучается сигнал «уведомлять», слот «onNotify» ставится в очередь на очередь событий. Поскольку у нас есть бесконечный цикл (пока мы не отменим его вручную) в слоте «doWork», слот «onNotify» не будет вызываться. Чтобы решить эту проблему, я могу сделать несколько вещей, а именно:

  1. Подключить сигнал «уведомлять» в слот «onNotify», используя флаг Qt :: DirectConnection. Таким образом, он выглядит как обычный вызов функции, выполняющийся в сигнальной нити.
  2. Иногда вызывайте метод QCoreApplication::processEvents(), чтобы заставить очередь событий обрабатываться.
  3. Неизвестное решение, которое я не знаю в это время:) ???

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

+0

Что такое "навсегда"? – HazemGomaa

+1

Это для бесконечных циклов в Qt http://doc.qt.io/qt-5/qtglobal.html#forever – Naidu

+0

@PAVANCHANDAKA спасибо! – HazemGomaa

ответ

5

Я не думаю, что здесь есть какое-нибудь «волшебное» решение; нить не может запускать цикл событий Qt, если он запускает собственный контур событий. На практике существует два основных решения, которые на самом деле две стороны одной медали:

  1. processEvents вызова() периодически из вашего цикла событий, как Вы предложили в вашем вопросе, так что Qt обработка событий код иногда запускается и обрабатывает входящие асинхронные сигналы.

  2. Не используйте длинную петлю в методе doWork(). Вместо этого выполните небольшую работу, сохраните результаты/состояние этой работы в переменной-члене или где-нибудь, а затем вызовите что-то вроде QTimer :: singleShot (0, это, SLOT (doWork())), чтобы событие Qt цикл вызовет ваш метод doWork() снова сразу после первого вызова doWork(). Таким образом, цикл событий Qt никогда не удерживается дольше, чем (короткий) период времени, затраченный на один вызов doWork().

Из этих двух вариантов, я думаю, что второй является более предпочтительной, поскольку он позволяет цикл обработки событий Qt для работы в нормальном режиме, а также позволяет избежать потенциального Срабатывания-над-своим собственным шнурки вопрос - - например предположите, что при использовании решения (1) ваш вызов processEvents() вызывает вызов слота, который удаляет объект BarWorker. Когда вызов processEvents() возвращается, BarWorker :: doWork() возобновит выполнение, но в этот момент все локальные переменные-члены и виртуальные методы, которые он может получить как часть обычного выполнения, были уничтожены, а их чтение или запись приведет к неопределенному поведению (если вам повезет, легкому отладку). Этот возможный snafu не может произойти при использовании решения (2), поскольку, если объект BarWorker удаляется между вызовами doWork(), любой асинхронный вызов в doWork() в очереди будет безопасно отменен.

1

Идиома для цикла forever, который взаимодействует с циклом события, представляет собой таймер с нулевой продолжительностью. Мы можем учитывать это в WorkerBase класс, где единица работы должно быть сделано в workUnit метода:

// https://github.com/KubaO/stackoverflown/tree/master/questions/worker-timer-40369716 
#include <QtCore> 

// See http://stackoverflow.com/q/40382820/1329652 
template <typename Fun> void safe(QObject * obj, Fun && fun) { 
    Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread()); 
    if (Q_LIKELY(obj->thread() == QThread::currentThread())) 
     return fun(); 
    struct Event : public QEvent { 
     using F = typename std::decay<Fun>::type; 
     F fun; 
     Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {} 
     Event(const F & fun) : QEvent(QEvent::None), fun(fun) {} 
     ~Event() { fun(); } 
    }; 
    QCoreApplication::postEvent(
      obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun))); 
} 

class WorkerBase : public QObject { 
    Q_OBJECT 
    QBasicTimer timer_; 
protected: 
    virtual void workUnit() = 0; 
    void timerEvent(QTimerEvent *event) override { 
     if (event->timerId() == timer_.timerId() && timer_.isActive()) 
      workUnit(); 
    } 
public: 
    using QObject::QObject; 
    Q_SIGNAL void finished(); 
    /// Thread-safe 
    Q_SLOT void virtual start() { 
     safe(this, [=]{ 
      timer_.start(0, this); 
     }); 
    } 
    /// Thread-safe 
    Q_SLOT void virtual stop() { 
     safe(this, [=]{ 
      if (!isActive()) return; 
      timer_.stop(); 
      emit finished(); 
     }); 
    } 
    bool isActive() const { return timer_.isActive(); } 
    ~WorkerBase() { 
     if (isActive()) emit finished(); 
    } 
}; 

Рабочие становятся:

class FooWorker : public WorkerBase 
{ 
    Q_OBJECT 
    int counter = 0; 
    bool isDone() const { return counter >= 10; } 
    void workUnit() override { 
     if (!isDone()) { 
      counter ++; 
      emit notify(counter); 
      QThread::sleep(1); 
     } else 
      stop(); 
    } 
public: 
    void start() override { 
     counter = 0; 
     WorkerBase::start(); 
    } 
    void stop() override { 
     if (!isDone()) emit aborted(); 
     WorkerBase::stop(); 
    } 
    Q_SIGNAL void notify(int); 
    Q_SIGNAL void aborted(); 
}; 

class BarWorker : public WorkerBase 
{ 
    Q_OBJECT 
    void workUnit() override { 
     QThread::sleep(1); 
    } 
public: 
    void stop() override { 
     emit aborted(); 
     WorkerBase::stop(); 
    } 
    Q_SIGNAL void aborted(); 
    Q_SLOT void onNotify(int value) 
    { 
     qDebug() << "Notification value:" << value; 
    } 
}; 

Обратите внимание, что сигнализирует aborted() и finished() имеют разные значения.

Наконец, тест Жгут:

class Thread : public QThread { public: ~Thread() { quit(); wait(); } }; 

int main(int argc, char ** argv) { 
    QCoreApplication app{argc, argv}; 

    BarWorker barWorker; 
    FooWorker fooWorker; 
    Thread barThread, fooThread; 
    barWorker.moveToThread(&barThread); 
    fooWorker.moveToThread(&fooThread); 
    barWorker.start(); 
    fooWorker.start(); 

    QObject::connect(&fooWorker, &FooWorker::finished, &app, &QCoreApplication::quit); 
    QObject::connect(&fooWorker, &FooWorker::notify, &barWorker, &BarWorker::onNotify); 

    fooThread.start(); 
    barThread.start(); 
    return app.exec(); 
} 

#include "main.moc" 

Если вы получаете QBasicTimer::stop: Failed. Possibly trying to stop from a different thread предупреждение, это не имеет значения, и является результатом ошибки Qt.

Смежные вопросы