Qt - 运行

时间:2016-09-08 17:14:37

标签: c++ qt

我试图从数据库中获取大数据,但在运行主窗口时冻结。

我正在Windows下工作,根据此link Windows会在5秒后自动将程序设置为挂起状态。

有没有办法防止冻结?

以下是代码:

void MainWindow::on_getDataButtonClicked()
{
    ui->centralWidget->setEnabled(false);
    QApplication::setOverrideCursor(Qt::WaitCursor);
    try
    {
        Client client(user, password);

        std::future<map<string, map<string, string> > > fut =
                  std::async(std::launch::async, &Client::get_data, &client);

        // While not all data has been retrieved, set message to the status bar.
        while (fut.wait_for(std::chrono::seconds(0)) != std::future_status::ready)
        {
            ui->statusBar->showMessage("Getting data.");
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
            ui->statusBar->showMessage("Getting data..");
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
            ui->statusBar->showMessage("Getting data...");
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
        }

        map<string, map<string, string> > exported_strings = std::move(fut.get());

        ui->statusBar->showMessage("%All data has been retrieved!");
    }
    catch (std::string& s)
    {
        QMessageBox::critical(this, "Error", QString::fromStdString(s));
    }
    catch (std::exception& e)
    {
        QMessageBox::critical(this, "Error", QString(e.what()));
    }
    catch (...)
    {
        QMessageBox::critical(this, "Error", "An unknown error has occurred.");
    }
    ui->centralWidget->setEnabled(true);
    QApplication::restoreOverrideCursor();
}

侧面说明,调试时主窗口不会冻结。

3 个答案:

答案 0 :(得分:3)

如果您正在等待它并在while循环中阻止GUI线程,那么进行异步工作是没有意义的。你需要摆脱while循环。

您可以使用QtConcurrent::run代替std::async,并在异步任务完成时使用QFutureWatcher以异步方式获得通知,而不会阻止。

// https://github.com/KubaO/stackoverflown/tree/master/questions/async-sane-39396761
#include <QtWidgets>
#include <QtConcurrent>
#include <map>
#include <string>

struct Client {
    using result_type = std::map<std::string, std::map<std::string, std::string>>;
    result_type get_data() {
        QThread::sleep(5); // pretend to do some work
        return result_type();
    }
};

class MainWindow : public QMainWindow {
    Q_OBJECT
    Client::result_type exported_strings;
    QWidget centralWidget;
    QVBoxLayout layout{&centralWidget};
    QPushButton getDataButton{"Get Data"};
    QStatusBar statusBar;
    QTimer statusTimer;
    QString statusMessage;

    void setBusyStatus(const QString & status) {
        centralWidget.setEnabled(false);
        QApplication::setOverrideCursor(Qt::WaitCursor);
        statusMessage = status;
        statusTimer.start(0);
    }
    void setNormalStatus(const QString & status) {
        centralWidget.setEnabled(true);
        QApplication::restoreOverrideCursor();
        statusBar.showMessage(status);
        statusTimer.stop();
    }
    Q_SLOT void on_getDataButtonClicked();
public:
    MainWindow() {
        setStatusBar(&statusBar);
        setCentralWidget(&centralWidget);
        layout.addWidget(&getDataButton);
        int n = 0;
        connect(&statusTimer, &QTimer::timeout, [=]() mutable {
            statusBar.showMessage(QStringLiteral("%1%2").arg(statusMessage).arg(QString{n+1, QChar{'.'}}));
            n = (n+1)%3;
            statusTimer.start(500);
        });
        connect(&getDataButton, &QPushButton::clicked, this, &MainWindow::on_getDataButtonClicked);
    }
};

void MainWindow::on_getDataButtonClicked()
{
    auto future = QtConcurrent::run([=]{
        Client client;
        return client.get_data();
    });
    auto watcher = new QFutureWatcher<Client::result_type>{this};
    connect(watcher, &QFutureWatcher<Client::result_type>::finished, this, [=]{
        exported_strings = std::move(watcher->result());
        watcher->deleteLater();
        setNormalStatus("All data has been retrieved!");
    });
    watcher->setFuture(future);
    setBusyStatus("Getting data");
}

int main(int argc, char ** argv) {
    QApplication app{argc, argv};
    MainWindow w;
    w.show();
    return app.exec();
}
#include "main.moc"

或者,您可以从异步代码发出信号,如果您愿意,可以保留std::async的使用权:

#include <QtWidgets>
#include <future>
#include <map>
#include <string>

struct Client {
    using result_type = std::map<std::string, std::map<std::string, std::string>>;
    result_type get_data() {
        QThread::sleep(5); // pretend to do some work
        return result_type();
    }
};

class MainWindow : public QMainWindow {
    Q_OBJECT
    Client::result_type exported_strings;
    QWidget centralWidget;
    QVBoxLayout layout{&centralWidget};
    QPushButton getDataButton{"Get Data"};
    QStatusBar statusBar;
    QTimer statusTimer;
    QString statusMessage;

    std::future<Client::result_type> resultFuture;

    void setBusyStatus(const QString & status) {
        centralWidget.setEnabled(false);
        QApplication::setOverrideCursor(Qt::WaitCursor);
        statusMessage = status;
        statusTimer.start(0);
    }
    void setNormalStatus(const QString & status) {
        centralWidget.setEnabled(true);
        QApplication::restoreOverrideCursor();
        statusBar.showMessage(status);
        statusTimer.stop();
    }
    Q_SLOT void on_getDataButtonClicked();
    Q_SIGNAL void hasResult();
public:
    MainWindow() {
        setStatusBar(&statusBar);
        setCentralWidget(&centralWidget);
        layout.addWidget(&getDataButton);
        int n = 0;
        connect(&statusTimer, &QTimer::timeout, [=]() mutable {
            statusBar.showMessage(QStringLiteral("%1%2").arg(statusMessage).arg(QString{n+1, QChar{'.'}}));
            n = (n+1)%3;
            statusTimer.start(500);
        });
        connect(&getDataButton, &QPushButton::clicked, this, &MainWindow::on_getDataButtonClicked);
    }
};

void MainWindow::on_getDataButtonClicked()
{
    connect(this, &MainWindow::hasResult, this, [this](){
        exported_strings = std::move(resultFuture.get());
        setNormalStatus("All data has been retrieved!");
    }, Qt::UniqueConnection);

    resultFuture = std::async(std::launch::async, [this]{
        Client client;
        auto result = client.get_data();
        emit hasResult();
        return result;
    });
    setBusyStatus("Getting data");
}

int main(int argc, char ** argv) {
    QApplication app{argc, argv};
    MainWindow w;
    w.show();
    return app.exec();
}
#include "main.moc"

答案 1 :(得分:0)

如果一切都发生在同一个线程上并且某些操作很慢,那么你就不会及时回到事件循环,操作系统不会声明你卡住了。

解决方案是:

1)使用多个流程。

2)使用多个线程。

3)将你的一个线程上的工作划分为块,这些块都保证足够小,以便你能够及时回到为事件循环服务。

答案 2 :(得分:-2)

为了避免多线程,这是你应该做的:

  • 将代码(实际上,只是在思考中)剪切成块
  • 运行您的程序并研究这些块,看看哪些块导致冻结(执行时间最长)
  • 一旦您知道导致冻结的部分,请使用QApplication::processEvents();强制MainWindow变得敏感。

示例:让我举个例子来说明。假设你有这个功能

void MainWindow::on_getDataButtonClicked()
{
    //do some easy stuff
    for(long i = 0; i < 100000; i++)
    {
        //do some stuff
        //do some other stuff
    }
    //do some different stuff
}

现在执行这个讨厌的功能时,你的窗口肯定会冻结,直到整个功能完成。 “简单的东西”不是问题,因为它很快。但是大循环就是问题所在。因此,您所要做的就是告诉Qt重新处理使主窗口再次响应的事件;像这样:

void MainWindow::on_getDataButtonClicked()
{
    //do some easy stuff
    for(long i = 0; i < 100000; i++)
    {
        QApplication::processEvents();
        //do some stuff
        //do some other stuff
    }
    QApplication::processEvents();
    //do some different stuff
}

您应该拨打多少次processEvents();?只要您希望窗口再次响应,请调用它。如果有疑问,只需将processEvents()放在任何地方!用这种方式感染每一条线。这不是最好的做法,但是你可以逐个删除它们,看看你的程序再次开始冻结。

另外一条建议:不要抛出std::string例外。这是一个非常糟糕的主意。了解如何抛出从std::exception继承的类。您仍然可以将字符串保存在std::exception对象中。例如,您可以执行:throw std::exception("This is very very bad");。你还可以投入很多其他课程。找到他们here