"Зависает" окно QProgressDialog при взаимодействии с запущенным вторичным потоком

Рейтинг: 0Ответов: 2Опубликовано: 17.08.2023

QProgressDialog используется для отображения результатов объёмного расчёта, запущенного в отдельном потоке. Причём объект QProgressDialog создаётся в основном потоке приложения

// Создаём окно диалога для отображения состояния расчёта
QProgressDialog *progressDialog = new QProgressDialog("Выполняется расчёт...", "&Отмена", 0, 10, mainWindow, Qt::WindowFlags() | Qt::WindowStaysOnTopHint);
progressDialog->setWindowTitle("Подождите");
progressDialog->setAutoReset(false);
progressDialog->setMinimumDuration(0);
progressDialog->show();
connect(progressDialog, SIGNAL(canceled()), this, SLOT(cancel_work()) );

// Создаём нить кода для расчёта
ThreadSetExactData *threadSetExactData = new ThreadSetExactData(..., progressDialog);
connect(threadSetExactData, SIGNAL(finished()), threadSetExactData, SLOT(work_done()));
connect(threadSetExactData, SIGNAL(finished()), threadSetExactData, SLOT(deleteLater()));
threadSetExactData->start();
threadSetExactData->setPriority(QThread::HighestPriority);

Поток взаимодействует с диалогом с помощью механизма сигналов и слотов

class ThreadSetExactData : public QThread
{
    Q_OBJECT
public:
    explicit ThreadSetExactData(..., QProgressDialog *_progressDialog, QObject *parent = nullptr);
    ~ThreadSetExactData();
protected:
    void run() override;

private slots:
    void work_done();

private:
    QProgressDialog *progressDialog;

signals:
    void set_progress_maximum(int maximum);
    void set_progress_value(int value);
};

ThreadSetExactData::ThreadSetExactData(..., QProgressDialog *_progressDialog, QObject *parent) : QThread(parent), progressDialog(_progressDialog)
{
    ...
    connect(this, SIGNAL(set_progress_maximum(int)), progressDialog, SLOT(setMaximum(int)));
    connect(this, SIGNAL(set_progress_value(int)), progressDialog, SLOT(setValue(int)));
}

void ThreadSetExactData::run()
{
    TotalWork = work();
    emit set_progress_maximum(TotalWork);

    ...
    
    while(have_something_to_do())
    {
        ...
        CurrentWork = current_iteration_work();
        emit set_progress_value(CurrentWork);
    }
}

Взаимодействие работает нормально (сигналы потока эмитируются и обрабатываются), но только окно диалога не воспринимает действие пользователя, в частности, нажатие на кнопку Cancel. Окно вообще не воспринимает события, кнопка Cancel "зависает", и, следовательно, никак не эмитирует сигнал canceled(). Что довольно странно, т.к. диалог запущен в основном потоке и как будто на нём должен действовать цикл обработки событий основного приложения.

В чём может быть проблема?

Ответы

▲ 0

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

Во-вторых, мне не удалось воспроизвести вашу проблему.

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

Поток:

class Thread : public QThread
{
    Q_OBJECT
public:
    Thread(QObject *parent = nullptr);
    void run() {
        int i = 0, max = 100;
        emit set_progress_maximum(max);

        while(i++ < max) {
            emit set_progress_value(i);
            QThread::msleep(500);
        }
    }

signals:
    void set_progress_maximum(int value);
    void set_progress_value(int value);
};

Главное окно, слот нажатия на кнопку, запуск потока, отображение окна прогресса (все работает как и ожидается, ничего не зависает):

void MainWindow::on_pushButton_clicked()
{
    QProgressDialog* dialog = new QProgressDialog();
    Thread* th = new Thread();
    connect(th, &Thread::set_progress_maximum, dialog, &QProgressDialog::setMaximum);
    connect(th, &Thread::set_progress_value, dialog, &QProgressDialog::setValue);

    connect(dialog, &QProgressDialog::canceled, this, [&]() {
        qDebug() << "stop";
    });

    th->start();
    dialog->show();
}

Window 11 Qt 6.5.1 MinGW x64

▲ -1

К сожалению, проблему решить не удалось. В отладчике проверено, что сигнал QProgressDialog::canceled попадает на слот для обработки (как указано в коде ответа, {qDebug() << "stop";}) лишь после завершения потока, когда в нём уже нет надобности. К тому же, эмиссия этого сигнала вызывается принудительным закрытием окна диалога close(), а не нажатием на кнопку cancel.