【问题标题】:Qt C++ Timer inside a QThread and send data from QThread to GUI ThreadQThread中的Qt C++ Timer并将数据从QThread发送到GUI线程
【发布时间】:2021-12-25 06:30:40
【问题描述】:

我在一个线程中创建了一个计时器,在这个计时器中,char 值从 0 计数到 10。 我想在 GUI 上显示这个 char 值。我没有收到任何错误,但我启动时 GUI 冻结。

你能帮我看看我哪里做错了吗?

main.cpp

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);

MainWindow w;
w.show();
return a.exec();
}

主窗口.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include "mythread.h"
#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    MyThread *mThread;

private:
    Ui::MainWindow *ui;

public slots:
    void onNumberChanged(char);
};

#endif // MAINWINDOW_H

主窗口.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    mThread = new MyThread(this);
    connect(mThread,SIGNAL(NumberChanged(char)), this, SLOT(onNumberChanged(char)));
    mThread->start();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::onNumberChanged(char Number)
{
    ui->label->setText(QString::number(Number));
}

mythread.h

#include <QThread>
#include <QTimer>
#include <QObject>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    void run();

signals:
    void NumberChanged(char);

public slots:
    void update();

private:
    QTimer *timer;

};

mythread.cpp

#include "mythread.h"
#include <QtCore>

char i=45;
QString mystr = "mytext";

MyThread::MyThread(QObject *parent) :
    QThread(parent)
{
    timer= new QTimer();
    connect(timer,SIGNAL(timeout()),this,SLOT(update()));
    timer->start(10);
}

void MyThread::update()
{
    for( i = 0; i<10; i++){
    emit NumberChanged(i);
    this->msleep(500);
    }
    if (i>0)
        i=0;
}

void MyThread::run()
{
}

【问题讨论】:

  • MyThreadQTimer 设置为每10 毫秒调用一次update(),但在update() 内你调用msleep(500) 10 次,这意味着update() 将花费超过5 秒返回。其中一件事必须要完成;如果您想经常拨打update(),则需要快速返回。
  • 请阅读 Qt 中的线程和线程亲和性。您的代码清楚地表明您正在使用试错法来了解线程。虽然这种方法可能适用于 Qt 的某些其他领域,但它绝对不适用于线程......原因是线程可能经常看起来正常工作,即使它们没有正常工作。对于线程,您需要了解机制的逻辑。您需要了解线程对象“存在”在主 GUI 线程中,但它的 run() 方法是在线程本身中执行的。顺便提一句。为什么你的run() 是空的?这没有任何意义。
  • 顺便说一句。不要再使用带有SIGNALSLOT 宏的旧式连接。它已经过时了很长时间。我敢打赌你不使用 Qt4,对吗?
  • 也不要使用全局变量...(除非你真的必须)
  • @JeremyFriesner 好的,如果我删除 msleep(500),一切都好吗? @V.K.谢谢你,我会读的。我在run() 中初始化、连接并启动了计时器,它仍然无法正常工作。我正在使用 Qt 5。我不知道用什么来代替 SIGNALSLOT 这就是我使用的原因。我可以用什么代替?

标签: c++ qt qt5 qthread qtimer


【解决方案1】:

您需要在 run() 函数中创建计时器,而不是在构造函数中。 因为在 GUI 线程中调用构造函数,并且在此函数中创建的每个对象都在 GUI 线程中。或者您可以调用 moveToThread 让您的计时器移动到您的线程。

【讨论】:

  • 好吧,我明白了,你是对的。我在 run() 函数中创建并启动了计时器。但是还是不行。
  • 请检查是否在新线程中调用了 update() 方法。在连接结束时添加连接类型(Qt::queueConnection)并检查主线程是否与带有 QThread::currentThreadId() 的 update() 方法调用者不同。
  • 当然,您需要在代码中的两个连接信号槽中添加连接类型(Qt::queueConnection)。
【解决方案2】:

关于 QTimer 的几件事,直接来自docs(强调我的):

在多线程应用程序中,您可以在任何具有事件循环的线程中使用 QTimer。要从非 GUI 线程启动事件循环,请使用 QThread::exec()。 Qt 使用计时器的线程亲和性来确定哪个线程将发出 timeout() 信号。因此,您必须在其线程中启动和停止计时器;无法从另一个线程启动计时器。

这对您意味着您需要在QThread 子类的run 函数中创建计时器。为什么?因为那是在 QThread 管理的底层线程上实际运行的方法 (docs)。

不幸的是,这还不足以让您工作。原因是一旦线程退出run 函数,该线程上的事件循环将停止,因此计时器将不再工作(如我的第一段所述)。因此,您需要在这里做的是确保 run 函数不会退出(通过使用无限循环),以便事件循环保持活动状态。然后你需要找到一种方法来退出我不会进入的循环。如您所见,它的参与度越来越高。

解决这个问题的一个简单方法是不继承 QThread 并使用工作线程。在下面的代码中,QTimer 是 worker 的数据成员。计时器的父级是工人本身;这是为了确保a)一旦工人被移动到新线程,计时器也会被移动,并且b)当工人被删除时计时器将被删除。最后,process 函数将在新线程上运行,我们将在该函数中启动计时器并建立所需的连接。

class Worker : public QObject
{
    Q_OBJECT

public:
    Worker(QObject* parent = nullptr)
        : QObject(parent)
    {
        // the timer object MUST be a child of the worker
        // this will ensure that when the worker changes thread affinity so will the timer
        mTimer = new QTimer(this);
    }

public slots:
    void process()
    {
        connect(mTimer, &QTimer::timeout, this, &Worker::update);
        mTimer->start(0);
    }

private:
    void update()
    {
        for( auto i = 0; i!= 10; i++)
        {
            emit NumberChanged(i);
            // set whatever number suits you here for sleeping
            QThread::msleep(200);
        }
    }

signals:
    void NumberChanged(char i);

private:
    QTimer* mTimer;
};

至于MainWindow,只需将指向QThread 成员的指针更改为常规QThread,如下所示:

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    QThread mThread;

private:
    Ui::MainWindow *ui;

public slots:
    void onNumberChanged(char);
};

并按如下方式实现构造函数和析构函数。在构造函数中,我们进行必要的连接以确保 a) 线程完成时删除工作线程,以及 b) 线程启动时启动工作线程(即调用进程)。后一种连接也保证了process槽实际上会在新线程上运行,从而确保定时器也在正确的线程中启动;有关详细信息,请参阅QueuedConnection

在析构函数中,我们要求线程退出并等待它处理完所有待处理的请求。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , mThread()
{
    ui->setupUi(this);
    auto worker = new Worker();
    worker->moveToThread(mThread);
    connect(&mThread, &QThread::finished, worker, &QObject::deleteLater);
    connect(&mThread, &QThread::started, worker, &Worker::process);
    connect(worker, &Worker::NumberChanged, this, &Widget::onNumberChanged);
    mThread.start();
}


MainWindow::~MainWindow()
{
    mThread.quit();
    mThread.wait();
    delete ui;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-21
    • 1970-01-01
    • 2013-05-23
    • 1970-01-01
    • 2018-05-17
    • 2015-10-17
    • 1970-01-01
    相关资源
    最近更新 更多