避免多线程操作中的 GUI 冻结

Posted

技术标签:

【中文标题】避免多线程操作中的 GUI 冻结【英文标题】:Avoiding GUI freezing on multithreaded operations 【发布时间】:2014-06-27 07:58:59 【问题描述】:

我有一个 Qt GUI 类 preferencesWindow,显然它负责处理用户偏好。我有一些管理与数据库服务器的连接的字段。当一个字段离开时,dbsChanged() 方法被调用。以下是我设法编写的一些代码:

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;

由于get_databases 可能需要很长时间,所以我认为将其放入单独的线程中,正如您在这两行中看到的那样:

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

可以解决问题。它在单独的线程上运行,但仍会冻结 GUI(在工作时)。

我应该如何重写整个过程?我想到了一些解决方案,但我不确定它们的性能,我不想无用地工作......

【问题讨论】:

【参考方案1】:

它冻结了 GUI,因为即使 get_databases 调用在单独的线程中,您仍然等待导致冻结的结果。

我不知道如何在 Qt 中执行此操作,但正常情况是打开一个对话框说“请稍候”或带有取消按钮的内容,并让工作线程向父级发送信号(GUI ) 线程完成。

【讨论】:

这会导致性能下降,因为该过程可能需要 0.5 秒到 30 秒 @Victor 在某些情况下可能会导致性能变差(例如只需要 0.5 秒)。如果操作通常需要至少几秒钟,那将如何导致性能下降?在这种情况下,设置所有内容(显示对话框、开始运行)和拆除(发送信号、关闭对话框)的时间可以忽略不计。 @Victor:约阿希姆是对的。 Futures 最好与 QFutureWatcher 结合使用,当你的 Future 完成时,它会向你的父线程发出信号。在当前代码中使用 Future 没有多大意义。【参考方案2】:

当您调用loader.result() 时,QFuture 将等待线程设置结果。您必须稍后等待该值。

我想您可以将未来对象存储为preferencesWindow 的成员,并在完成get_databases 时向自己发送signal。所以你给你的应用程序时间在这个等待时间内处理其他事件。

【讨论】:

【参考方案3】:

您可以使用QFutureWatcher 来监控QFuture 对象的状态,如文档中所述:

// 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);

【讨论】:

以上是关于避免多线程操作中的 GUI 冻结的主要内容,如果未能解决你的问题,请参考以下文章

多线程 Python Tkinter 串行监视器中的按钮问题

28初识线程

c#多线程应用程序中的界面冻结

子线程中的 waitForReadyRead() 冻结 GUI 线程

在 GTK 中使用多线程?

Pyside2 QGuiApplication,Gui冻结按钮点击