以 "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);
既然已经清除了,您的代码中还有其他缺陷。
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 文档中经常看到可重入一词。
祝你的多线程之旅顺利。
非常好 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;
}
使用这个程序,我按下 '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);
既然已经清除了,您的代码中还有其他缺陷。
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 文档中经常看到可重入一词。
祝你的多线程之旅顺利。
非常好 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;
}