以"right"的方式进行QThreading

Doing QThread-ing the "right" way

本文关键字:QThreading 方式进 right      更新时间:2023-10-16

使用此程序,我按下"运行"按钮和 for 循环循环 100 次(延迟 100 毫秒)并在 txt 字段中打印周期计数

我已经成功地用一个派生自QThread的MyThread对象完成了它。它有效。我可以使用"停止"按钮中断循环。

然而,它被严厉警告说,从QThread派生一个对象是非常糟糕的。所以我做到了他们建议的另一种方式,"正确"的方式。

而且它不起作用。我可以在控制台上获取循环周期编号,但不能进入文本框

"运行"按钮会关闭,在 100 个周期完成之前再也不会出现。文本字段显示 99。

这是代码,我做错了什么?

// MyWidget.h   SF022
#ifndef MYWIDGET_H_
#define MYWIDGET_H_
#include <QtWidgets/QWidget>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton>
#include "../MyThread.h"
#include "../MyObject.h"
#include <QThread>
class MyWidget : public QWidget {
    Q_OBJECT
public:
    MyWidget(QWidget* parent = 0);
    ~MyWidget();
    QPushButton* pbRun;
    QPushButton* pbStop;
    QPushButton* pbExit;
    QLineEdit*   txtCount;
    QThread* cThread;
    MyObject* myObject;
public slots:
    void onNumberChanged(int);
private slots:
    void pbRun_Slot();
    void pbStop_Slot();
    void pbExit_Slot();
};
#endif /* MYWIDGET_H_ */
------------------------------------------------
// MyWidget.cpp
#include "MyWidget.h"
#include "../K.h"
#include <iostream>
MyWidget::MyWidget(QWidget* parent) : QWidget(parent) {
    pbRun = new QPushButton(this);
    pbRun->setObjectName(QStringLiteral("pbRun"));
    pbRun->setGeometry(QRect(20, 20, 80, 40));
    pbRun->setText("Run");
    connect(pbRun, SIGNAL(clicked()), this, SLOT(pbRun_Slot()));
    pbStop = new QPushButton(this);
    pbStop->setObjectName(QStringLiteral("pbStop"));
    pbStop->setGeometry(QRect(20, 80, 80, 40));
    pbStop->setText("Stop");
    connect(pbStop, SIGNAL(clicked()), this, SLOT(pbStop_Slot()));
    pbExit = new QPushButton(this);
    pbExit->setObjectName(QStringLiteral("pbExit"));
    pbExit->setGeometry(QRect(20, 140, 80, 40));
    pbExit->setText("Exit");
    connect(pbExit, SIGNAL(clicked()), this, SLOT(pbExit_Slot()));
    txtCount = new QLineEdit(this);
    txtCount->setGeometry(QRect(20, 200, 80, 40));
    txtCount->setStyleSheet("QLineEdit{background: white;}");
//  myObject holds the cycling mechanism
    myObject = new MyObject(this);
//  the myObject sends each new cycle number out here
    connect(myObject, SIGNAL(numberChanged(int)), this, SLOT(onNumberChanged(int)));
}
MyWidget::~MyWidget() {
}
void MyWidget::pbRun_Slot() {
//  start thread
    cThread = new QThread(this);
    myObject->doSetup(*cThread);
    myObject->moveToThread(cThread);
    cThread->start();
}
void MyWidget::pbStop_Slot() {
//   stop the thread
   myObject->Stop = true;
}
void MyWidget::pbExit_Slot() {
//  a static pointer to the main window
   (K::SfMainWin)->close();
}
// a slot
void MyWidget::onNumberChanged(int j) {
//  output the cycle count to a text field
    txtCount->setText(QString::number(j));
}
----------------------------------------------------------
// MyObject.h
#ifndef MYOBJECT_H_
#define MYOBJECT_H_
#include <QObject>
#include <QThread>
class MyObject : public QObject {
    Q_OBJECT
public:
    explicit MyObject(QObject* parent = 0);
    ~MyObject();
    void doSetup(QThread&);
    bool Stop;
signals:
    void numberChanged(int);
public slots:
    void doWork();
};
#endif /* MYOBJECT_H_ */
----------------------------------------------------------
// MyObject.cpp    
#include "MyObject.h"
#include <QMutex>
#include <iostream>
#include "string.h"
MyObject::MyObject(QObject* parent) : QObject(parent) {
    Stop = false;
}
MyObject::~MyObject() {
}
void MyObject::doSetup(QThread& cThread) {
    Stop = false;
    connect(&cThread, SIGNAL(started()), this, SLOT(doWork()));
}
void MyObject::doWork() {
    for (int i = 0; i < 100; i++) {
        QMutex mutex;
        mutex.lock();
        if (this->Stop) {
            break;
        }
//  output into a text field
        emit numberChanged(i);
//  output on the console
        std::cout << "running " << (QString::number(i)).toStdString() << std::endl;
        mutex.unlock();
        QThread::msleep(100);
    }
}

myObject 永远不会移动到您创建的线程中。一切都在主线程中执行。因为

myObject = new MyObject(this);

要将 QObject 移动到另一个线程,他不应该有父线程。如果是这样,Qt会默默地告诉你出了什么问题(通过在输出上打印,与不正确的连接相同)。框架设计不会对这种类型的警告感到恐慌......

它应该是

myObject = new MyObject(0);

现在,这已被清除,代码中还有其他缺陷。

  1. QMutex mutex;是本地的,将始终由同一线程获取。 这意味着他没有目的。相反,它应该是MyObject的私人成员
  2. MyWidget::pbStop_Slot应该是一种MyObject的方法,否则在访问Stop成员时会出现争用条件。还记得上面的互斥锁吗?现在是使用它的时候了。顺便说一下,使用您的实现直接调用该方法,因为cThread的偶数循环仅在执行doWork

    MyObject::pbStop_Slot()
    {
        mutex.lock()
        Stop = true;
        mutex.unlock()
    }
    
  3. 现在您的程序在技术上应该是正确的。但是你不能使用信号和插槽很糟糕,因为你的线程被阻止执行doWork.另外,我们可以对那个锁做点什么吗?事实上,是的。我将采用的方法是使用 Qtimer 作为心跳 ech 100ms,而不是要求线程休眠。但是为了不改变你的代码,你可以直接使用QAbstractEventDispatcher * QAbstractEventDispatcher::instance ( QThread * thread = 0 )

    MyObject::pbStop_Slot() //becomes a real slot again
    {
        // no more mutex
        Stop = true;
    }
    ....
    //this connection is changed
    connect(pbStop, SIGNAL(clicked()), myObject, SLOT(pbStop_Slot()));
    ....
    void MyObject::doWork() {
    for (int i = 0; i < 100; i++) {
        //no mutex
        if (this->Stop) {
            break;
        }
        //  output into a text field
        emit numberChanged(i);
        //  output on the console
        std::cout << "running " << (QString::number(i)).toStdString() << std::endl;
       //process events, to allow stop to be processed using signals and slots
       QAbstractEventDispatcher::instance(cThread)->processEvents();
        QThread::msleep(100);    
    }
    

    }

  4. 关于processEvents警告。就像现在一样,如果用户在执行dowork时按下run它将在自身内部调用。你现在有一个讨厌的代码。规避这种情况的一种简单方法是放置一个布尔值,该布尔值在dowork开始时被检查和设置。

    dowork(){
      if(isdoingwork)
        return;
      isdoingwork = true
      for(...
    

这是实现重入的穷人方式。你会在Qt文档中经常看到"重入"一词。

祝您在多线程旅行中好运。

非常好的UmNyobe!

作为参考,我在此处添加了更正后的代码。

// MyWidget.h   
#ifndef MYWIDGET_H_
#define MYWIDGET_H_
#include <QtWidgets/QWidget>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton>
#include "../MyObject.h"
#include <QThread>
class MyWidget : public QWidget {
    Q_OBJECT
public:
    MyWidget(QWidget* parent = 0);
    ~MyWidget();
    QPushButton* pbRun;
    QPushButton* pbStop;
    QPushButton* pbExit;
    QLineEdit*   txtCount;
    QThread* wThread;
    MyObject* myObject;
public slots:
    void onNumberChanged(int);
private slots:
    void pbRun_Slot();
//  void pbStop_Slot();
    void pbExit_Slot();
};
#endif /* MYWIDGET_H_ */
-----------------------------------------
// MyWidget.cpp
#include "MyWidget.h"
#include "../K.h"
#include <iostream>
MyWidget::MyWidget(QWidget* parent) : QWidget(parent) {
    pbRun = new QPushButton(this);
    pbRun->setObjectName(QStringLiteral("pbRun"));
    pbRun->setGeometry(QRect(20, 20, 80, 40));
    pbRun->setText("Run");
    connect(pbRun, SIGNAL(clicked()), this, SLOT(pbRun_Slot()));
    pbStop = new QPushButton(this);
    pbStop->setObjectName(QStringLiteral("pbStop"));
    pbStop->setGeometry(QRect(20, 80, 80, 40));
    pbStop->setText("Stop");
    pbExit = new QPushButton(this);
    pbExit->setObjectName(QStringLiteral("pbExit"));
    pbExit->setGeometry(QRect(20, 140, 80, 40));
    pbExit->setText("Exit");
    connect(pbExit, SIGNAL(clicked()), this, SLOT(pbExit_Slot()));
    txtCount = new QLineEdit(this);
    txtCount->setGeometry(QRect(20, 200, 80, 40));
    txtCount->setStyleSheet("QLineEdit{background: white;}");
    myObject = new MyObject(0);
    connect(myObject, SIGNAL(numberChanged(int)), this, SLOT(onNumberChanged(int)));
    connect(pbStop, SIGNAL(clicked()), myObject, SLOT(pbStop_Slot()));
}
MyWidget::~MyWidget() {
    delete myObject;
    delete wThread;
}
void MyWidget::pbRun_Slot() {
//  start QThread*, wThread in the MyWidget class
    wThread = new QThread(this);
    myObject->doSetup(wThread);
    myObject->moveToThread(wThread);
    wThread->start();
}
void MyWidget::pbExit_Slot() {
//  a static pointer of the main window
    (K::SfMainWin)->close();
}
void MyWidget::onNumberChanged(int j) {
    txtCount->setText(QString::number(j));
}
---------------------------------------------------------
// MyObject.h
#ifndef MYOBJECT_H_
#define MYOBJECT_H_
#include <QObject>
#include <QThread>
#include <QMutex>
class MyObject : public QObject {
    Q_OBJECT
public:
    explicit MyObject(QObject* parent = 0);
    ~MyObject();
    void doSetup(QThread*);
    int  hold;
    bool Stop;
    int  inx;
        int  lastUsedInx;
signals:
    void numberChanged(int);
public slots:
    void doWork();
    void pbStop_Slot();
private:
    bool      isdoingwork;
    QThread*  pThread;
    QMutex    mutex;
};
#endif /* MYOBJECT_H_ */
----------------------------------------------------
// MyObject.cpp    SF022
#include "MyObject.h"
#include <iostream>
#include <QAbstractEventDispatcher>
MyObject::MyObject(QObject* parent) : QObject(parent) {
    Stop = false;
    isdoingwork = false;
    inx = 0;
    lastUsedInx = 0;
}
MyObject::~MyObject() {
}
void MyObject::doSetup(QThread* thread) {
    pThread = thread;
    Stop = false;
    isdoingwork = false;
    connect(pThread, SIGNAL(started()), this, SLOT(doWork()));
}
void MyObject::pbStop_Slot() {
    mutex.lock();
    Stop = true;
    isdoingwork = false;
    mutex.unlock();
}
void MyObject::doWork() {
    if(isdoingwork) {
        return;
    }
    isdoingwork = true;
    for (inx = lastUsedInx + 1; inx < 100; inx++) {
        if (this->Stop) {
            break;
        }
//  output into a text box
        emit numberChanged(inx);
        lastUsedInx = inx;
//  process events, to allow stop to be processed using signals and slots
        (QAbstractEventDispatcher::instance(pThread))->processEvents(QEventLoop::AllEvents);
        QThread::msleep(800);
    }
    isdoingwork = false;
}