Qt Thread + Gui (multithreaded application with interface)

Hello everyone, I will be very grateful to anyone who will help to deal with this problem. Recently I started studying threads (QThread). Due to the fact that I faced the problem of freezing the interface with large calculations.

The main thread of the QThread GUI hangs and, as I understood, you can only work with the Gui in it, using signals and slots.

My task is as follows:

You need to put the function runCalculation() in a separate thread, but so that the interface is drawn normally, no hangups. The interface itself is located in calcprogressdialog, there is only a progressBar for counting elements in the sheet, according to the formula from runCalculation()

Here is the formula itself:

int regionCalcGranularity = 100 / listKA->count();

And then in runCalculation() everything is output:

calculationControl->ui->progressBar->setValue(
calculationControl->ui->progressBar->value()+regionCalcGranularity);

Also in runCalculation, functions from ballistic.h are called, but as I understood, it is not necessary to touch it, only runCalculation()

Here is the code mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPushButton>
#include "calcprogressdialog.h"
#include "ballistic.h"
// Менеджеры соединения с БД
#include "lbd_manager.h"
// Глобальные настройки и параметры
#include "global.h"

namespace Ui {
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:

    // Загрузка перечня ИДПл
    void loadAllIDPl();
    void runCalculation();

private:
    manager_lbd::ManagerLbd * dbManager;
    CalcProgressDialog * calculationControl;
    Ballistic * ballistic;
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H 

Code mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include "ui_calcprogressdialog.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    dbManager = new manager_lbd::ManagerLbd(this);
    calculationControl = new CalcProgressDialog(this);
    ballistic = new Ballistic(this);

    this->loadAllIDPl();
    connect(this->planEditWidget->ui->pushButton_toCalculation, SIGNAL(pressed()), this, SLOT(runCalculation()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

// Запуск расчёта
void MainWindow::runCalculation()
{
    mAssert(calculationControl);
    mAssert(ballistic);

    mAssert(dbManager);

    int idplNum = 5; // возьмем число 5 как тестовое
    if( idplNum == 0 ) return;

    calculationControl->show();

    manager_lbd::list * tmp;

    // Сохранение нужных списков, чтобы те не перезатирались
    tmp = dbManager->selectAllIdplKa(idplNum);
    mAssert(tmp);
    manager_lbd::list * listKA = new manager_lbd::list;
    *listKA = *tmp;

    tmp = dbManager->selectAllIdplTz(idplNum);
    mAssert(tmp);
    manager_lbd::list * listTZ = new manager_lbd::list;
    *listTZ = *tmp;

    tmp = dbManager->selectAllIdplOkik(idplNum);
    mAssert(tmp);
    manager_lbd::list * listOKIK = new manager_lbd::list;
    *listOKIK = *tmp;

    manager_lbd::Idpl * IDPl = dbManager->selectOneIdpl(idplNum);
    mAssert(IDPl);

    // Получим ожидаемый номер, присваиваемый расчёту
    int calcNum = dbManager->nextvalCalculation();

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

    // Собираем список РЦР
    sGeoZone * Geo = new sGeoZone[listTZ->count()];

    // Текущий РЦР
    uint32_t currentGeo = 0;

    // Для каждой из записей в составе ИДПл
    QListIterator<manager_lbd::TableList> it(*listTZ);
    while(it.hasNext())
    {
        const manager_lbd::TableList * t = &it.next();

        // Получаем конкретный номер РЦР
        manager_lbd::IdplTz * IDPlTZ = dbManager->selectOneIdplTz(t->idNum);
        mAssert(IDPlTZ);

        manager_lbd::Zones * TZ = dbManager->selectOneZones(IDPlTZ->idTz);
        mAssert(TZ);

        // Запишем полученный номер
        Geo[currentGeo].num = TZ->idTz;

        // Получаем точки выбранного РЦР
        manager_lbd::list * TZPoints = dbManager->selectAllTzPoints(TZ->idTz);
        mAssert(TZPoints);

        Geo[currentGeo].count = TZPoints->count();

        // Заполняем массив точками
        int currentPoint=0;
        QListIterator<manager_lbd::TableList> pit(*TZPoints);
        while(pit.hasNext())
        {
            const manager_lbd::TableList * t = &pit.next();

            manager_lbd::TzPoints * point = dbManager->selectOneTzPoints(t->idNum);
            mAssert(point);

            Geo[currentGeo].point[currentPoint].x = point->x;
            Geo[currentGeo].point[currentPoint].y = point->y;
            Geo[currentGeo].point[currentPoint].z = point->z;

            dOut << "TZ: " << TZ->idTz << ", point: " << currentPoint <<
                ", --- "
                << Geo[currentGeo].point[currentPoint].x << ", "
                << Geo[currentGeo].point[currentPoint].y << ", "
                << Geo[currentGeo].point[currentPoint].z;

            currentPoint++;
        }
        currentGeo++;
    }

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

    // На сколько перемещается progressbar за одну операцию расчёта?
    int regionCalcGranularity = 100 / listKA->count();

    // Получаем КА и по одному отдаём их в метод расчёта
    calculationControl->ui->label->setText("Расчёт интервалов прохождения РЦР...");
    calculationControl->ui->progressBar->setValue(0);
    QCoreApplication::processEvents();

    // Список, куда сохраняются результаты, чтобы быть потом скопом записанными
    // в ЛБД в рамках одной транзакции
    QList<manager_lbd::ResDTtZ> resDTtZList;

    QListIterator<manager_lbd::TableList> kit(*listKA);
    while(kit.hasNext())
    {
        const manager_lbd::TableList * t = &kit.next();

        manager_lbd::IdplKa * IDPlKA = dbManager->selectOneIdplKa(t->idNum);
        mAssert(IDPlKA);

        //manager_lbd::KAList * KA = dbManager->selectOneKAList(IDPlKA->idSputnik);
        //mAssert(listKATmp);

        manager_lbd::list * ICList = dbManager->selectAllEntryCond(IDPlKA->idSputnik);
        mAssert(ICList);

        // TODO: добавить получение ПОСЛЕДНИХ НУ для выбранного КА,
        // а не ПЕРВЫХ ПОПАВШИХСЯ, как сейчас
        manager_lbd::EntryCond * IC = dbManager->selectOneEntryCond(ICList->at(0).idNum);

        QDateTime tn = IDPl->idplStartTime;
        QDateTime tk = IDPl->idplEndTime;

        //QDateTime tn(QDate(2015, 7,1), QTime(0, 20, 24, 32));
        //QDateTime tk(QDate(2015, 7,2), QTime(18, 51, 44, 32));

        QList<CR> * result = NULL;

        QCoreApplication::processEvents();
        result = ballistic->calculateRegions(IC, 125, tn, tk, Geo, currentGeo);
        QCoreApplication::processEvents();
        calculationControl->ui->progressBar->setValue(
            calculationControl->ui->progressBar->value()+regionCalcGranularity);
        QCoreApplication::processEvents();


        for(int i=0; i<result->count(); i++)
        {
            // TODO: Оптимизировать под пакетное сохранение
            manager_lbd::ResDTtZ resDTtZ;

            resDTtZ.idResDtTz= -1; //qrand();
            resDTtZ.idCalculation = calcNum; // этот номер получит расчёт

            resDTtZ.idSputnik = result->at(i).Nka;
            resDTtZ.idTz = result->at(i).Ncr;
            resDTtZ.idPair=result->at(i).Nka; // хм, разве это здесь нужно?
            //resDTtZ.idUser = 1; // костыль!
            resDTtZ.numRev = result->at(i).Nv;
            resDTtZ.tgBegin = ballistic->convertMsecToDateTime (result->at(i).dtvx);
            resDTtZ.tgMid = ballistic->convertMsecToDateTime (result->at(i).dtmid);
            resDTtZ.tgEnd = ballistic->convertMsecToDateTime (result->at(i).dtvux);

            // Добавляем результат в промежуточный список
            resDTtZList.push_back(resDTtZ);
            //dbManager->insertResDTtZ(&resDTtZ, 1);
        }

        sDelete(result);
    }

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

        // Собираем список ОКИК
        sOKIK * OKIK = new sOKIK[listOKIK->count()];

        // Текущий РЦР
        uint32_t currentOKIK = 0;

        // Для каждой из записей в составе ИДПл
        QListIterator<manager_lbd::TableList> it2(*listOKIK);
        while(it2.hasNext())
        {
            const manager_lbd::TableList * t = &it2.next();

            // Получаем конкретный номер ОКИК
            manager_lbd::IdplOkik * IDPlOkik = dbManager->selectOneIdplOkik(t->idNum);
            mAssert(IDPlOkik);

            // Получаем конкретный ОКИК
            manager_lbd::OKIK * cOKIK = dbManager->selectOneOkik(IDPlOkik->idOkik);
            mAssert(cOKIK);

            // Запишем полученные данные
            OKIK[currentOKIK].Nip = cOKIK->idOkik;
            OKIK[currentOKIK].rop.x = cOKIK->latitude;
            OKIK[currentOKIK].rop.y = cOKIK->longitude;
            OKIK[currentOKIK].rop.z = cOKIK->altitude;

            currentOKIK++;
        }

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

    int zrvCalcGranularity = 100 / listKA->count();

    // Получаем КА и по одному отдаём их в метод расчёта
    calculationControl->ui->label->setText("Расчёт ЗРВ...");
    calculationControl->ui->progressBar->setValue(0);
    QCoreApplication::processEvents();

    QList<manager_lbd::ResRVokik> resRvOkikList;

    QListIterator<manager_lbd::TableList> kit2(*listKA);
    while(kit2.hasNext())
    {
        const manager_lbd::TableList * t = &kit2.next();

        manager_lbd::IdplKa * IDPlKA = dbManager->selectOneIdplKa(t->idNum);
        mAssert(IDPlKA);

        manager_lbd::list * ICList = dbManager->selectAllEntryCond(IDPlKA->idSputnik);
        mAssert(ICList);

        // TODO: добавить получение ПОСЛЕДНИХ НУ для выбранного КА,
        // а не ПЕРВЫХ ПОПАВШИХСЯ, как сейчас
        manager_lbd::EntryCond * IC = dbManager->selectOneEntryCond(ICList->at(0).idNum);

        QDateTime tn = IDPl->idplStartTime;
        QDateTime tk = IDPl->idplEndTime;
        //QDateTime tn(QDate(2015, 7,1), QTime(0, 20, 24, 32));
        //QDateTime tk(QDate(2015, 7,1), QTime(21, 36, 04, 32));

        QList<ZRV> * result = NULL;

        QCoreApplication::processEvents();
        result = ballistic->calculateSights(IC, 125, tn, tk, OKIK, currentOKIK);
        QCoreApplication::processEvents();
        calculationControl->ui->progressBar->setValue(
            calculationControl->ui->progressBar->value()+zrvCalcGranularity);
        QCoreApplication::processEvents();

        for(int i=0; i<result->count(); i++)
        {
            // TODO: Оптимизировать под пакетное сохранение
            manager_lbd::ResRVokik resRvOkik;

            resRvOkik.idKey = -1; // qrand();
            resRvOkik.idCalculation = calcNum;  // этот номер получит расчёт
            resRvOkik.idSputnik = result->at(i).Nka;
            resRvOkik.idOkik = result->at(i).Nokik;
            resRvOkik.numRev = result->at(i).Nv;
            //resRvOkik.idUser = 1; // костыль!
            resRvOkik.rvz0begin = ballistic->convertMsecToDateTime (result->at(i).dtvx0);
            resRvOkik.rvz7begin = ballistic->convertMsecToDateTime (result->at(i).dtvx7);
            resRvOkik.rvz0end = ballistic->convertMsecToDateTime (result->at(i).dtvux0);
            resRvOkik.rvz7end = ballistic->convertMsecToDateTime (result->at(i).dtvux7);

            resRvOkikList.push_back(resRvOkik);
            //dbManager->insertResRVokik(&resRvOkik, 1);
        }

        sDelete(result);
    }

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

    // Записываем результаты в ЛБД
    // Преобразуем каждый из списков в массив:
    manager_lbd::ResDTtZ * resDTtZ = new manager_lbd::ResDTtZ[resDTtZList.count()];
    manager_lbd::ResRVokik * resRvOkik = new manager_lbd::ResRVokik[resRvOkikList.count()];


    dOut << "----------------------------------------------------------";

    // Создадим в ЛБД расчёт:
    manager_lbd::Calculation newCalculation;
    newCalculation.idplNum = idplNum;
    newCalculation.dtCalc = QDateTime::currentDateTime(); // в последней
                           // версии вроде бы перекрывается при записи в БД
    newCalculation.idCalculation = -1; // автогенерируемое, совпадёт с calcNum
    newCalculation.idUser = 1; // а вот это - костыль!

    // Запишем в ЛБД расчёт
    int calcId = dbManager->insertCalculation(&newCalculation, 1);
    // TODO: Проверить на наличие ошибки сохранения!

    // Собираем массивы
    for(int i=0; i<int(resDTtZList.count()); i++)
    {
        resDTtZ[i] = resDTtZList[i];
        resDTtZ[i].idCalculation = calcId;
    }

    for(int i=0; i<int(resRvOkikList.count()); i++)
    {
        resRvOkik[i] = resRvOkikList[i];
        resRvOkik[i].idCalculation = calcId;
    }

    // И запишем массивы одной большой транзакцией
    dbManager->insertResDTtZ(resDTtZ, resDTtZList.count());
    dbManager->insertResRVokik(resRvOkik, resRvOkikList.count());


    dOut << "----------------------------------------------------------";

    calculationControl->close();

    delete listKA;
    delete listTZ;
    delete listOKIK;
    // TODO: запамятовал синтаксис - проверить, правильно ли удаляются массивы?

    delete [] resDTtZ;
    delete [] resRvOkik;

    loadAllIDPl();
}
Author: Alexander Chernin, 2016-07-05

1 answers

If taking the calculation to a separate thread is necessary in order for the interface to correctly show the progress during the calculation, I would do something similar:

void MainWindow::calculate()
{
    QProgressDialog dialog(this);
    connect(this,&MainWindow::progressMax,&dialog,&QProgressDialog::setMaximum);    //сообщаем диалогу максимум прогресса
    connect(this,&MainWindow::progress,&dialog,&QProgressDialog::setValue);
    connect(this,&MainWindow::finished,this,&MainWindow::finalizeCalculation);
    dialog.show();
    QtConcurrent::run(this,&MainWindow::runCalculation);    //разово запускаем функцию в отдельном потоке
}

void MainWindow::runCalculation()
{
    emit progressMax(N);
    for (int i=0; i<N; i++)
    {
        doSomething();
        emit progress(i);
    }
    emit finished();
}

void MainWindow::finalizeCalculation()
{
    doSomethingFinally();
}

It is also necessary, if necessary, to prohibit calling the calculation again while the previous calculation is still in progress and to take care of canceling the calculation from the progress dialog.

ZY. The code was written from memory, syntax errors are possible.

 4
Author: Bearded Beaver, 2016-07-06 05:28:51