【问题标题】:QDialog box showing blank when MainWindow thread is busy当 MainWindow 线程忙时,QDialog 框显示空白
【发布时间】:2020-11-30 21:14:39
【问题描述】:

我正在为 Raspberry Pi 驱动的机器人开发基于 Qt-C++ 的前端应用程序。我正在使用 Qt 5.9 版以及 QSerialPort 和 Pigpio 库。在我的应用程序中,当我向机器人发出命令序列的运行命令时,我的 Raspberry Pi 开始与微控制器进行串行通信,在该微控制器中它发送一些消息,然后等待接收响应。这种发送和等待会导致 Mainwindow 线程冻结。我正在尝试构建一个紧急停止功能,这将在运行过程中停止命令执行。

为了实现这一目标,我尝试将我的串行通信部分推送到一个单独的线程 (QThread)。它没有成功。现在我正在尝试将紧急停止部分构建到一个 QDialog 框中,当我发出运行命令时会打开该对话框,其中包含一个紧急停止 QPushbutton。对话框正在以非模态形式运行。但是在我当前的代码中,当我给出运行命令时,会打开一个对话框,但是对话框是完全空白的,然后在运行命令结束时关闭(这是故意的)。我会分享一些外观截图。

你能建议我可能出错的地方吗?或者有没有更好的方法来解决这个问题?欢迎任何批评和建议!

谢谢!

【问题讨论】:

  • 您的程序等待响应可能是问题所在。您可以对收到的消息(readyRead 信号)做出反应,而不是等待响应。没有看到任何代码很难说。
  • @thuga 我最初也是这么想的。我不能使用它的原因是因为我想运行 1 个命令,等待下级控制器的 OK 响应,然后执行下一个命令。我的紧急停止需要在等待期间工作。我需要暂停执行下一个命令。在这种情况下您有什么建议吗?

标签: c++ qt raspberry-pi4 qtserialport


【解决方案1】:

不应阻塞 Qt 中的主线程。每次调用阻塞函数时,您的 GUI 以及对话框都会冻结。

一种解决方案是使用信号/插槽。它们很好地融入了 Qt。但是做一个复杂的请求/响应逻辑需要一个巨大的状态机,通常容易出错。

有时最好让这段代码阻塞,创建一个简单的请求/响应代码链,然后把它放在另一个非 GUI 线程中。然后使用信号通知主线程作业结果。

为了停止执行,可以使用原子并在阻塞步骤之间检查它。退出工作函数前的最大延时是单个阻塞函数的最大延时。您应该仔细调整超时。或者您可以编写自己的函数,模拟超时和停止条件。它应该检查传入数据是否在无限循环中可用,并检查每次迭代的停止条件,这必须是超时和停止条件变量。

// pseudocode here
while (true) {
    if (stopCondition) return; // check for emergency condition
    it (currentTime - startTime > timeout) return;
    if (serial->dataReady()) break;
}
auto data = serial->getData();

如果一个步骤可以永远阻塞,那么这个方法就不能用了。

有一个 QtConcurrent 框架的例子,它演示了 QFuture 的使用和一个函数在一个单独的线程中的工作,而不阻塞主线程。您可以将所有通信逻辑放入其中。 代码仅供参考!

#ifndef WORKERCLASS_H
#define WORKERCLASS_H

#include <QObject>
#include <QtConcurrent/QtConcurrent>
#include <QFuture>

class WorkerClass : public QObject
{
    Q_OBJECT
public:
    explicit WorkerClass(QObject *parent = nullptr) : QObject(parent) {
        connect(&futureWatcher, &QFutureWatcher<void>::finished, [this] () {
            emit workFinsihed();
        });
    }

    void startWork(int value) {
        atomic = 0;
        future = QtConcurrent::run(this, &WorkerClass::workFunction, value);
        futureWatcher.setFuture(future);
    }

    void stopWork() {
        atomic = 1;
    }

private:
    QFuture<void> future;
    QFutureWatcher<void> futureWatcher;

    void workFunction(int value) {
        for (int i = 0; i < value; ++i) {
            if (atomic) return;
        }
        return;
    };

    QAtomicInt atomic{0};

signals:
    void workFinsihed();
};

#endif // WORKERCLASS_H

【讨论】:

  • 那么您是建议我最终将串行通信代码放在单独的线程中吗?实际上,我之前通过将 QSerialPort 代码放入 QThread 中进行了尝试。但最终,我的代码部分与命令序列的迭代循环(在运行按钮的 clicked() 插槽中)不得不等待响应。我是否错误地假设以任何方式尝试等待响应,我的停止按钮将保持无响应?另外我不认为while循环会给出不同的结果。如果需要,我将分享我的代码。它有很多糟糕的数学。
  • 我建议几件事。 1.使用另一个线程,但使用QFuture。 2. 一步一步同步编写通讯代码 3. 添加一个急停的停止条件变量。 4. 为每个阻塞函数设置小超时并检查调用之间的停止条件或编写自定义函数来检查数据就绪和停止条件。然后您的 GUI 代码将保持快速,按钮将起作用;通信代码将同步且简单;您将有一个机制来停止通信周期并发送紧急停止。
猜你喜欢
  • 2013-11-30
  • 1970-01-01
  • 2012-01-04
  • 1970-01-01
  • 1970-01-01
  • 2012-09-17
  • 2015-08-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多