Предотвращение зависания графического интерфейса при многопоточных операциях

#c #multithreading #qt #user-interface

#c #многопоточность #qt #пользовательский интерфейс

Вопрос:

У меня есть класс Qt GUI preferencesWindow , который, очевидно, отвечает за обработку пользовательских настроек. У меня есть несколько полей, которые управляют подключением к серверу базы данных. Когда поле остается, dbsChanged() вызывается метод. Ниже приведен некоторый код, который мне удалось написать:

 void preferencesWindow::dbsChanged() {
    QFuture<QStringList> loader = run(this, amp;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;
}
 

Поскольку get_databases это может занять много времени, я подумал, что включение отдельного потока, как вы можете видеть в этих двух строках:

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

может решить проблему. Он выполняется в отдельном потоке, но все равно замораживает графический интерфейс (во время работы).

Как я должен переписать весь этот процесс?Я думал о некоторых решениях, но я не совсем уверен в их производительности, и я не хочу работать бесполезно…

Ответ №1:

Это замораживает графический интерфейс, потому что, хотя get_databases вызов выполняется в отдельном потоке, вы все равно ждете результатов, что вызывает замораживание.

Я не знаю, как это сделать в Qt, но нормальным было бы открыть диалоговое окно с надписью «пожалуйста, подождите» или что-то в этом роде с помощью кнопки отмены, и по завершении рабочий поток отправляет сигнал родительскому потоку (GUI).

Комментарии:

1. Это приведет к снижению производительности, поскольку процесс может занять от 0,5 секунды до 30 секунд

2. @Victor В некоторых ситуациях это может привести к снижению производительности (например, когда это займет всего 0,5 секунды). Если операция обычно занимает не менее пары секунд, как это может привести к снижению производительности? В этом случае время на настройку всего (отображение диалогового окна, запуск протектора) и демонтаж (отправка сигнала, закрытие диалогового окна) будет незначительным.

3. @Victor: Иоахим прав. Фьючерсы лучше всего использовать в сочетании с QFutureWatcher, который сигнализирует вашему родительскому потоку, когда ваше будущее завершено. Использование Future, как в вашем текущем коде, не имеет особого смысла.

Ответ №2:

QFuture будет ждать, пока поток не установит результат при вашем вызове loader.result() . Вам придется дождаться этого значения позже.

Я думаю, вы могли бы сохранить будущий объект как член preferencesWindow и отправить себе signal , когда закончите get_databases . Таким образом, вы даете своему приложению время для обработки других событий в течение этого времени ожидания.

Ответ №3:

Вы можете использовать QFutureWatcher для отслеживания этого состояния QFuture объекта, как написано в документации:

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

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