在函数调用期间暂时禁用 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();
};

我希望 MainWindowDoWork 函数表现得像:

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::futureqApp->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 操作。计算将在它自己的线程中进行。

这样你就可以随心所欲了:

  1. window 在工作时被禁用
  2. window 在工作时保持响应(可以移动和调整大小)
  3. 扔进 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"