Victor
Victor

Reputation: 14632

Avoiding GUI freezing on multithreaded operations

I have a Qt GUI class preferencesWindow that, obviously, is responsible for handling the user preferences. I have some fields that manage the connection to a database server. When a field is left, dbsChanged() method is called. Below is some code I managed to write:

void preferencesWindow::dbsChanged() {
    QFuture<QStringList> loader = run(this, &preferencesWindow::get_databases);
    QStringList databases = loader.result();

    if (databases.length()) {
        this->ui.database->show();
        this->ui.nodb_label->hide();
        this->ui.database->clear();
        this->ui.database->addItems(databases);
        this->ui.okButton->setDisabled(false);
        this->ui.validationStatus->setPixmap(QPixmap(":/icon/tick.png"));
    } else {
        this->ui.database->hide();
        this->ui.nodb_label->show();
        this->ui.okButton->setDisabled(true);
        this->ui.validationStatus->setPixmap(QPixmap(":/icon/error.png"));
    }
}
QStringList preferencesWindow::get_databases() {
    QSqlDatabase test_connection;
    if (QSqlDatabase::contains("PREFEREMCES_LIVE_TEST_CONNECTION"))
        test_connection = QSqlDatabase::database("PREFEREMCES_LIVE_TEST_CONNECTION");
    else test_connection = QSqlDatabase::addDatabase("QMYSQL", "PREFEREMCES_LIVE_TEST_CONNECTION");
    test_connection.setHostName(this->ui.serverAddress->text());
    test_connection.setUserName(this->ui.username->text());
    test_connection.setPassword(this->ui.password->text());
    test_connection.setDatabaseName(this->ui.database->currentText());
    test_connection.setPort(this->ui.serverPort->value());

    test_connection.open();
    qDebug() << "Error: " << test_connection.lastError();
    QSqlQuery show_databases = test_connection.exec("show databases");
    QStringList databases;
    while (show_databases.next()) {
        databases.append(show_databases.value(0).toString());
    }
    QSqlDatabase::removeDatabase("PREFERENCES_LIVE_TEST_CONNECTION");
    return databases;
}

Since get_databases can take a long time, I thought that putting in on a separate thread as you can see in these two lines:

QFuture<QStringList> loader = run(this, &preferencesWindow::get_databases);
QStringList databases = loader.result();

could solve the problem. It runs on a separate thread, but it still freezes the GUI (while working).

How should I rewrite this entire process? I though of some solutions, but I am not really sure about their performance, and I don't want to work uselessly...

Upvotes: 1

Views: 770

Answers (3)

Victor
Victor

Reputation: 14632

You can use QFutureWatcher to monitor that status of the QFuture object, like written in the documentation:

// Instantiate the objects and connect to the finished signal.
MyClass myObject;
QFutureWatcher<int> watcher;
connect(&watcher, SIGNAL(finished()), &myObject, SLOT(handleFinished()));

// Start the computation.
QFuture<int> future = QtConcurrent::run(...);
watcher.setFuture(future);

Upvotes: 1

Stefan Weiser
Stefan Weiser

Reputation: 2292

The QFuture will wait until the thread sets the result when your call loader.result(). You have to wait for that value later.

I guess you could store the future object as member of preferencesWindow and send yourself a signal, when finishing get_databases. So you give your application time to process other events during this wait time.

Upvotes: 2

Some programmer dude
Some programmer dude

Reputation: 409482

It freezes the GUI because even though the get_databases call is in a separate thread, you still wait for the results which causes the freeze.

I don't know how to do it in Qt, but the normal thing would be to open a dialog saying "please wait" or something with a cancel button, and have the worker thread send a signal to the parent (GUI) thread when done.

Upvotes: 3

Related Questions