以“正确”的方式进行 QThread-ing

Posted

技术标签:

【中文标题】以“正确”的方式进行 QThread-ing【英文标题】:Doing QThread-ing the "right" way 【发布时间】:2015-01-26 19:01:08 【问题描述】:

使用这个程序,我按下“运行”按钮,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("QLineEditbackground: 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);

    


【问题讨论】:

查看以下链接以获得一些见解:mayaposch.wordpress.com/2011/11/01/… 如果这只是一个练习,那么可以,但通常你会用QTimer 而不是工作线程来做这样的事情。阅读this了解更多信息。 【参考方案1】:

myObject 从未移动到您创建的线程。一切都在主线程中执行。因为

myObject = new MyObject(this);

要将 QObject 移动到另一个线程,he should not have a parent。如果确实如此,Qt 会静默告诉你出了什么问题(通过在输出上打印,与不正确的连接相同)。框架设计不要对这种类型的警告感到恐慌......

应该是的

myObject = new MyObject(0);

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

    QMutex mutex; 是本地的,总是被同一个线程获取。这意味着他没有目的。相反,它应该是 MyObject 的私有成员

    MyWidget::pbStop_Slot 应该是MyObject 的方法,否则访问Stop 成员时会出现竞争条件。还记得上面的互斥锁吗?是时候使用它了。顺便说一句,你的实现直接调用方法,因为cThread的偶数循环只执行doWork

    MyObject::pbStop_Slot()
    
        mutex.lock()
        Stop = true;
        mutex.unlock()
    
    

    现在您的程序在技术上应该是正确的。但是很糟糕,您不能使用信号和插槽,因为您的线程被阻止执行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);    
    
    

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

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

这是实现reentrancy 的穷人方式。你会在 Qt 文档中经常看到 reentrant 这个词。

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

【讨论】:

【参考方案2】:

非常好的乌姆尼奥贝!

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

// 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("QLineEditbackground: 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;

【讨论】:

以上是关于以“正确”的方式进行 QThread-ing的主要内容,如果未能解决你的问题,请参考以下文章

以正确的方式使用天气数据对时间序列进行上采样

使用 typescript、react 和 graphql 以正确方式进行查询的问题

以编程方式进行Android会议和保持通话[重复]

如何以编程方式获取正确的 iOS 版本?

以正确的方式重新学习 CSS [关闭]

JWT 令牌认证 - 以正确的方式做事