Qt: Как избежать тупика, когда несколько сигналов в очереди вызывают один и тот же слот - PullRequest
0 голосов
/ 16 января 2019

В следующем коде я встречаю тупик в someOperation:

class A : public QObject {
    Q_OBJECT
public:
    explicit A(QObject* parent) : QObject(parent), data(0) {}
public slots:
    void slot1() {
        someOperation();
    }
    void slot2() {
        someOperation();
    }
    void slot3() {
        someOperation();
    }
private:
    void someOperation() {
        QMutexLocker lk(&mutex);
        data++;
        QMessageBox::warning(NULL, "warning", "warning");
        data--;
        assert(data == 0);
    }
    int data;
    QMutex mutex; //protect data
};

class Worker: public QThread {
    Q_OBJECT
public:
    explicit Worker(QObject* parent) : QThread(parent) {}
protected:
    virtual void run() {
        // some complicated data processing
        emit signal1();
        // other complicated data processing
        emit signal2();
        // much complicated data processing
        emit signal3();
        qDebug() << "end run";
    }
signals:
    void signal1();
    void signal2();
    void signal3();

};

int main(int argc, char *argv[])
{
        QApplication app(argc, argv);
        A* a = new A(&app);
        Worker* w = new Worker(a);
        QObject::connect(w, SIGNAL(signal1()), a, SLOT(slot1()), Qt::QueuedConnection);
        QObject::connect(w, SIGNAL(signal2()), a, SLOT(slot2()), Qt::QueuedConnection);
        QObject::connect(w, SIGNAL(signal3()), a, SLOT(slot3()), Qt::QueuedConnection);
        w->start();

        return app.exec();
}

Существует поток, который будет испускать три сигнала, все они поставлены в очередь, связанные с экземпляром класса A, и все слоты класса A 'будут вызывать someOperation, а someOperation защищен мьютексом и будет всплывать окно сообщения.

Qt :: QueuedConnection 2 Слот вызывается, когда управление возвращается в цикл обработки событий получателя. Слот выполняется в потоке получателя.

Кажется, что slot2 вызывается, когда окно сообщения slot1 все еще выполняет модальное действие в главном потоке, но в это время slot1 имеет блокировку mutex, поэтому тупик.

Как изменить код, чтобы избежать тупика?

Обновление: (17 января 2019 г.)

Мне нужен архив: слот2 не будет выполнен до завершения слота1.

Что следует сохранить:

  1. работник - это фоновый поток для обработки данных, стоит долго; так что, как бы то ни было, три сигнала будут исходить из другого потока.
  2. рабочий не должен блокироваться излучением сигналов.
  3. слоты должны выполняться в основном потоке, потому что они будут обновлять графический интерфейс.
  4. someOperation не возвращается.

Ответы [ 2 ]

0 голосов
/ 17 января 2019

Требование, чтобы "someOperation is not reentrant" было нечетным. Что должно произойти, если попытка возвращения? Учитывая, что someOperation может вызываться только из потока main, я вижу только два варианта ...

  1. Блокируйте полностью мьютексом / барьером и т. Д., Как вы пытались.
  2. Блок основан на счетчике уровня рекурсии и вращает цикл событий, пока этот счетчик не уменьшится до нуля.

1) Полностью заблокирует цикл событий потока, не позволяя текущему диалоговому окну функционировать правильно.

2) Разрешает одновременное ведение всех диалогов сообщений, а не их сериализацию.

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

Одним из вариантов может быть использование отдельного QObject производного экземпляра класса самостоятельно QThread. Подумайте о следующем ...

class signal_serialiser: public QObject {
  Q_OBJECT;
signals:
  void signal1();
  void signal2();
  void signal3();
};

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

QObject::connect(w, SIGNAL(signal1()), a, SLOT(slot1()), Qt::QueuedConnection);
QObject::connect(w, SIGNAL(signal2()), a, SLOT(slot2()), Qt::QueuedConnection);
QObject::connect(w, SIGNAL(signal3()), a, SLOT(slot3()), Qt::QueuedConnection);

Измените это на ...

signal_serialiser signal_serialiser;
QObject::connect(w, SIGNAL(signal1()), &signal_serialiser, SIGNAL(signal1()));
QObject::connect(w, SIGNAL(signal2()), &signal_serialiser, SIGNAL(signal2()));
QObject::connect(w, SIGNAL(signal3()), &signal_serialiser, SIGNAL(signal3()));

/*
 * Note the use of Qt::BlockingQueuedConnection for the
 * signal_serialiser --> A connections.
 */
QObject::connect(&signal_serialiser, SIGNAL(signal1()), a, SLOT(slot1()), Qt::BlockingQueuedConnection);
QObject::connect(&signal_serialiser, SIGNAL(signal2()), a, SLOT(slot2()), Qt::BlockingQueuedConnection);
QObject::connect(&signal_serialiser, SIGNAL(signal3()), a, SLOT(slot3()), Qt::BlockingQueuedConnection);
QThread signal_serialiser_thread;
signal_serialiser.moveToThread(&signal_serialiser_thread);
signal_serialiser_thread.start();

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

0 голосов
/ 16 января 2019

Это потому, что ваша функция void someOperation() не реентерабельная .

Статические функции QMessageBox охватывают собственный цикл обработки событий, который постоянно вызывает QCoreApplication::processEvents():

  1. Выполнение первого вызова someOperation() застревает на QMessageBox::warning(...).
  2. Там exec() звонит processEvents(), 3. который видит второй сигнал
  3. и вызывает someOperation() снова
  4. , когда попытка повторной блокировки mutex не удалась.

Как решить эту проблему, зависит от того, чего вы хотите достичь ...


О вашем общем подходе к QThread: Вы делаете это неправильно.
(Эта ссылка дает хорошее начало в тему, но не полное решение.)

Вы создаете и запускаете фоновый поток. Но этот поток только испустит три сигнала и затем закончится.

Слоты будут вызываться внутри основного (GUI) цикла событий, потому что это сходство потоков вашего A *a.

Чтобы слоты выполнялись в фоновом режиме, вам необходимо:

  1. создать свой экземпляр A без родителя: A *a = new A();
  2. создайте свой экземпляр Worker с приложением в качестве родителя: Worker *w = new Worker(&app); (или ни с чем, по крайней мере, с a)
  3. изменить сродство потока вашего экземпляра A: a->moveToThread(Worker);
  4. не переопределяйте Worker::run() или, если вы действительно хотите (см. Пункт 5), вызовите базовую реализацию: QThread::run();
  5. испускает сигналы из основного (вы можете испускать их из run (), но это не обязательно).
...