以 "right" 的方式进行 QThread

Doing QThread-ing the "right" way

使用这个程序,我按下 'run' 按钮和一个 for 循环循环 100 次(延迟 100 毫秒) 并在 txt 字段中打印循环计数

我已经使用派生自 QThread 的 MyThread 对象成功地完成了它。有用。我可以 使用 'stop' 按钮中断循环。

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

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

'run' 按钮会下降,直到 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 移动到另一个线程,he should not have a parent。如果确实如此,Qt 将 静默地 告诉您出了什么问题(通过在输出上打印,与不正确的连接相同)。框架设计是为了不对这种类型的警告感到恐慌...

应该是

myObject = new MyObject(0);

既然已经清除了,您的代码中还有其他缺陷。

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

    MyObject::pbStop_Slot()
    {
        mutex.lock()
        Stop = true;
        mutex.unlock()
    }
    
  4. 现在您的程序在技术上应该是正确的。但是很糟糕,你不能使用信号和槽,因为你的线程被阻止执行 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);    
    }
    

    }

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

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

这是实现 reentrancy 的穷人方式。您会在 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;
}