我试图从数据库中获取大数据,但在运行主窗口时冻结。
我正在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();
}
侧面说明,调试时主窗口不会冻结。
答案 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{¢ralWidget};
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(¢ralWidget);
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{¢ralWidget};
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(¢ralWidget);
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。