【问题标题】:Qt GUI math application hangs out GUI while computingQt GUI 数学应用程序在计算时挂起 GUI
【发布时间】:2013-09-30 13:43:09
【问题描述】:

我的应用程序有一个简单的 GUI = 我必须为我的数学函数设置一些输入参数,然后单击“计算”按钮,然后函数启动。数学函数是用纯 C 编写的,所以没有对象,只有函数。

看起来像这样:

#include "mymath.h"

class myMath : public QMainWindow
{
    Q_OBJECT

    // ...
    void compute();
};

void myMath::compute()
{
   //get parameters from gui
   call_C_fun();
   // save results to GUI
}

这段代码的主要问题是,当我单击“计算”时(它进行了大量计算,最多需要 5 分钟左右)它会挂起我的 GUI,所以我不能做任何其他事情(我可以​​'甚至看不到我的 GUI,窗口在计算运行期间被“冻结”。函数完成后,它在 QLabels 上打印结果,GUI 再次“活着”。我该如何解决这个问题?我没有当计算需要时间时,不希望我的 GUI 被“冻结”。有什么想法吗?我想过 QThread - 但我在线程方面有点新,所以请给新手简单易懂的答案: )

【问题讨论】:

  • 看看this。然后在线程对象中进行计算,并在完成后发送信号。将信号连接到班级中的一个插槽,然后对结果做任何你想做的事情。
  • class myMath后面还有两个:分号,因为...?

标签: c++ qt user-interface


【解决方案1】:

将计算分解到另一个线程,如下所示:-

// This is the object that will run the computation on a different thread
class Computation : public QObject
{
signals:
    void Finished();
public slots:
    void Compute();
};

然后在你的主窗口类中创建另一个线程并开始运行:-

class MyMath : public QMainWindow
{
    public:
        void StartComputation();
};


MyMath::StartComputation()
{
    QThread* pThread = new QThread;
    Computation* pComputation = new Computation;

    // move computation to the new thread
    pCompuation->moveToThread(pThread);

    // Note Qt5 connection style    

    // ensure the computation starts when the thread starts
    connect(pThread, &QThread::started, pComputation, &Computation::Compute); 
    // when computation is finished, quit the thread
    connect(pComputation, &Compute::Finished, pThread, &QThread::quit);
    // have the thread tidy up when finished
    connect(pThread, &QThread::finished, pThread, &QThread::deleteLater);
    // start it!
    pThread->start();
}

【讨论】:

  • 需要注意的是,connect 语法是 Qt 5 中新增的。
  • 这就是我评论它 Qt 5 的原因。
【解决方案2】:

尝试内置 QtConcurrent 命名空间,而不是滚动您自己的线程。它更容易、优化且不易出错。无需担心将内容移动到其他线程并使用互斥锁执行显式同步。

QtConcurrent::run 使用内部线程池来运行您的任务并返回一个QFuture,您可以使用QFutureWatcher 和信号和槽与之交互。名字听起来很复杂,但代码很简单:

class MyMath : public QObject 
{
    Q_OBJECT
public:

    explicit MyMath(QObject* parent) : QObject(parent) {
        this->watcher = new QFutureWatcher(this);
        connect(this->watcher, SIGNAL(finished()),
                this, SLOT(computationFinished()));
    }

public slots:

    void compute() {

        // This one is called by the user. Retrieve
        // the computation arguments (whatever they
        // may be), start the computation using 
        // QtConcurrent::run and prepare to receive
        // the results. 
        // 
        // Maybe update the UI to tell the user that 
        // the computationis being performed?

        Args args = getSomeArgs();
        QFuture<Result> res = QtConcurrent::run(this, &MyMath::internalCompute, args);
        this->watcher->setFuture(res);
    }

    void computationFinished() {
        // The computation is done. Update the GUI
        // to display the results. Done.
    }

private:

    QFutureWatcher<Result>* watcher;

    Args getSomeArgs() const {
        // return arguments needed by compute.
    }

    Result internalCompute(Args args) const {
        // perform very expensive math.
    }
};

更新。经过 Merlin069 的评论,我认为建议使用 QtConcurrent 提供的更复杂的操作是不明智的。

【讨论】:

【解决方案3】:

在您的情况下,线程将是一种解决方案。您的计算将在一个单独的线程中运行,这将允许您的 GUI 在计算您需要的任何内容时做出响应。

根据我的经验,这在 GUI 中很常见。 GUI 有自己的线程,您的逻辑使用自己的线程。

您可以使用非常简单的模式来处理这个问题,使用来自 GUI 的信号来启动工作人员,以及来自工作人员的信号来触发 GUI 中的更新。

您需要注意通常的线程安全问题。我强烈建议你学习一些关于线程的知识,以便更好地掌握你在做什么。它还可以帮助您了解在使用线程代码时可能会观察到的奇怪行为(这对于不习惯它的人来说并不总是微不足道的)。

我还建议您阅读QThread documentation,它很好地解释了如何在 Qt 中实现线程代码(尽管更喜欢单独的工作线程而不是 Qthread 的子类化)。看看QMutexQMutexLocker,你可能需要它们。

【讨论】:

  • 现在,它继承QThread 并使用QMutex - 效果很好,干杯!明天我将尝试这个主题的其他解决方案。顺便说一句 - 为什么子类化 QThread 被称为坏主意?
  • 回复。子类化QThread:读取this blog post
  • @yak Andref 链接了一篇相关文章。这并不总是不好,但通常你应该更喜欢工人方法。然后,这个链接也是相关的,关于Why it's not always bad to subclass QThread。非常简短的回答:如果您不需要事件循环、子类,如果您需要事件循环(信号、插槽等),则不要子类。
【解决方案4】:

Qt GUI 数学应用程序在计算时挂出 GUI

我的应用程序有一个简单的 GUI .....

这段代码的主要问题是,当我点击“计算”时(计算量很大,最多需要 5 分钟左右)它会挂起我的 GUI,所以我不能做任何其他事情(我什至看不到我的 GUI,窗口在计算运行时“冻结”。函数完成后,它在 QLabels 上打印结果,GUI 再次“活着”。我该如何解决这个问题?当计算需要时间时,我不希望我的 GUI 被“冻结”。有什么想法吗?我想过 QThread - 但我在线程方面有点新,所以请为新手提供简单易懂的答案:)

您的程序(GUI)似乎永远不会挂起。它使用“compute()”函数的“call_C_fun()”继续处理。但是 GUI 不是响应式的。请参阅http://doc.qt.digia.com/qq/qq27-responsive-guis.html 处的保持 GUI 响应式参考。 GUI 在长时间操作中反复出现的冻结问题可以通过多种方法进行处理。但最简单的方法是手动事件处理

要使用手动事件处理,必须在循环内定期调用 QCoreApplication::processEvents()

下面的例子展示了如何工作:

call_C_fun(){
....
while(Your_loop_condition) {
    ....
    QCoreApplication::processEvents();
    if (!your_compute_button->isChecked()) {
        //"Aborted"
        return;
    }
}
....
}

【讨论】:

    【解决方案5】:

    作为完整 QThread 解决方案的替代方案,您还可以使用 QEventLoop 创建一个单独的事件循环来运行您的计算,以保持主事件循环(托管 UI)空闲。它不像QThread 那样灵活,但它们的实施速度非常快。

    【讨论】:

    • 它们还暴露了一大堆问题,因为突然间你的整个 GUI 代码都变得可重入了。
    • 我认为只有将计算类混合到 GUI 中,而不是将它们分开并通过信号/插槽系统调用,GUI 代码才会变得可重入?我可能是错的,我已经很久没有研究过了。
    • 无论您在自己的代码中看到exec() 的位置,都可以重新输入任何QObject::event() 方法。这就是它所需要的。当然,从技术上讲,如果你只是为了好玩而创建一个事件循环,然后在不通过exec() 运行它的情况下将其销毁,你没问题,但肯定不是你的意思:)
    • 有趣,我不知道这一点,猜你每天都会学到新东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-05
    • 2013-04-05
    相关资源
    最近更新 更多