在函数调用期间暂时禁用 window
Temporarily disable window during a function call
考虑:
class Worker
{
public:
void DoWork();
};
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
Worker MyWorker;
public:
void DoWork();
};
我希望 MainWindow
的 DoWork
函数表现得像:
void MainWindow::DoWork()
{
setEnabled(false);
std::future<void> Resu = std::async(std::launch::async, &Worker::DoWork, &MyWorker);
while (Resu.wait_for(100ms) == std::future_status::timeout)
qApp->processEvents();
try
{ Resu.get(); }
catch (Except const &MyExcept)
{ QMessageBox::critical(this, "Error", MyExcept.What()); }
setEnabled(true);
}
这按我的意愿工作:(i) window 在工作时被禁用,
(ii) window 在工作时保持响应(可以移动和调整大小),以及 (iii) 任何
Except
扔在 Worker::DoWork
中被抓起来很方便。
但是,此解决方案依赖于 std::future
和 qApp->processEvents()
,后者
this answer.
未推荐
如何在 Qt 中正确编写上述代码?
到目前为止,我已经尝试过:
void MainWindow::DoWork()
{
setEnabled(false);
QFuture<void> Resu = QtConcurrent::run(std::bind(&Worker::DoWork, &MyWorker));
try
{ while (Resu.isRunning()) qApp->processEvents(); }
catch (Except const &MyExcept)
{ QMessageBox::critical(this, "Error", MyExcept.What()); }
setEnabled(true);
}
而且我发现了消耗整个线程的缺点(因为对 qApp->processEvents()
的调用太频繁),而且没有正确捕获异常。
您可以使用 QFutureWatcher
接收有关 QFuture
完成的通知,而无需忙于等待呼叫 processEvents
。
基本上,您订阅信号 QFutureWatcher<void>::finished
以了解操作何时完成。在关联的插槽中,您重新启用小部件。特别重要的是即使计算已经完成也要调用 future.waitForFinished()
,以强制转移在工作线程中抛出的任何 QException,请参阅 here。这看起来像是一种解决方法,但我不知道是否有更好的解决方案。
订阅后您调用 setFuture()
函数开始您的 doWork
操作。计算将在它自己的线程中进行。
这样你就可以随心所欲了:
- window 在工作时被禁用
- window 在工作时保持响应(可以移动和调整大小)
- 扔进
Worker::doWork
的任何 QException
都可以方便地抓到
这是一个基于您的问题的最小工作示例。它应该告诉你我的意思...
#include <QApplication>
#include <QException>
#include <QMessageBox>
#include <QPushButton>
#include <QThread>
#include <QtConcurrent>
class Exception : public QException
{
public:
Exception(const QString& message = "") : m_message {message} { }
void raise() const override { throw *this; }
Exception *clone() const override { return new Exception(*this); }
QString message() { return m_message; }
private:
QString m_message;
};
class Worker
{
public:
void doWork() {
QThread::sleep(5);
// throw Exception {"something bad happened..."};
QThread::sleep(5);
}
};
class Button : public QPushButton
{
Q_OBJECT
public:
Button(QWidget* parent = nullptr) : QPushButton {parent} {
resize(400, 300);
setText("Click me to wait!");
connect(this, &QPushButton::clicked,
this, &Button::doWork);
}
public slots:
void doWork() {
setEnabled(false);
auto future {QtConcurrent::run(&m_worker, &Worker::doWork)};
auto watcher {new QFutureWatcher<void> {}};
connect(watcher, &QFutureWatcher<void>::finished,
[=]() mutable {
try {
// Call this to force the transfer of any QException that was thrown in the worker thread
// (https://doc.qt.io/qt-5/qexception.html)
future.waitForFinished();
} catch (Exception& e) {
QMessageBox::critical(this, "Error", e.message());
}
setEnabled(true);
});
watcher->setFuture(future);
}
private:
Worker m_worker;
};
int main(int argc, char* argv[])
{
QApplication app {argc, argv};
auto button {new Button {}};
button->show();
return app.exec();
}
#include "main.moc"
考虑:
class Worker
{
public:
void DoWork();
};
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
Worker MyWorker;
public:
void DoWork();
};
我希望 MainWindow
的 DoWork
函数表现得像:
void MainWindow::DoWork()
{
setEnabled(false);
std::future<void> Resu = std::async(std::launch::async, &Worker::DoWork, &MyWorker);
while (Resu.wait_for(100ms) == std::future_status::timeout)
qApp->processEvents();
try
{ Resu.get(); }
catch (Except const &MyExcept)
{ QMessageBox::critical(this, "Error", MyExcept.What()); }
setEnabled(true);
}
这按我的意愿工作:(i) window 在工作时被禁用,
(ii) window 在工作时保持响应(可以移动和调整大小),以及 (iii) 任何
Except
扔在 Worker::DoWork
中被抓起来很方便。
但是,此解决方案依赖于 std::future
和 qApp->processEvents()
,后者
this answer.
如何在 Qt 中正确编写上述代码?
到目前为止,我已经尝试过:
void MainWindow::DoWork()
{
setEnabled(false);
QFuture<void> Resu = QtConcurrent::run(std::bind(&Worker::DoWork, &MyWorker));
try
{ while (Resu.isRunning()) qApp->processEvents(); }
catch (Except const &MyExcept)
{ QMessageBox::critical(this, "Error", MyExcept.What()); }
setEnabled(true);
}
而且我发现了消耗整个线程的缺点(因为对 qApp->processEvents()
的调用太频繁),而且没有正确捕获异常。
您可以使用 QFutureWatcher
接收有关 QFuture
完成的通知,而无需忙于等待呼叫 processEvents
。
基本上,您订阅信号 QFutureWatcher<void>::finished
以了解操作何时完成。在关联的插槽中,您重新启用小部件。特别重要的是即使计算已经完成也要调用 future.waitForFinished()
,以强制转移在工作线程中抛出的任何 QException,请参阅 here。这看起来像是一种解决方法,但我不知道是否有更好的解决方案。
订阅后您调用 setFuture()
函数开始您的 doWork
操作。计算将在它自己的线程中进行。
这样你就可以随心所欲了:
- window 在工作时被禁用
- window 在工作时保持响应(可以移动和调整大小)
- 扔进
Worker::doWork
的任何QException
都可以方便地抓到
这是一个基于您的问题的最小工作示例。它应该告诉你我的意思...
#include <QApplication>
#include <QException>
#include <QMessageBox>
#include <QPushButton>
#include <QThread>
#include <QtConcurrent>
class Exception : public QException
{
public:
Exception(const QString& message = "") : m_message {message} { }
void raise() const override { throw *this; }
Exception *clone() const override { return new Exception(*this); }
QString message() { return m_message; }
private:
QString m_message;
};
class Worker
{
public:
void doWork() {
QThread::sleep(5);
// throw Exception {"something bad happened..."};
QThread::sleep(5);
}
};
class Button : public QPushButton
{
Q_OBJECT
public:
Button(QWidget* parent = nullptr) : QPushButton {parent} {
resize(400, 300);
setText("Click me to wait!");
connect(this, &QPushButton::clicked,
this, &Button::doWork);
}
public slots:
void doWork() {
setEnabled(false);
auto future {QtConcurrent::run(&m_worker, &Worker::doWork)};
auto watcher {new QFutureWatcher<void> {}};
connect(watcher, &QFutureWatcher<void>::finished,
[=]() mutable {
try {
// Call this to force the transfer of any QException that was thrown in the worker thread
// (https://doc.qt.io/qt-5/qexception.html)
future.waitForFinished();
} catch (Exception& e) {
QMessageBox::critical(this, "Error", e.message());
}
setEnabled(true);
});
watcher->setFuture(future);
}
private:
Worker m_worker;
};
int main(int argc, char* argv[])
{
QApplication app {argc, argv};
auto button {new Button {}};
button->show();
return app.exec();
}
#include "main.moc"