欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

VS+QT多线程实现——run和moveToThread

程序员文章站 2022-03-23 09:25:30
VS+QT多线程实现——run和moveToThread实现方法及特性run代码实现moveToThread代码实现讨论工程源码写在前头:最近学习多线程开发,以小白的视角写一些学习心得。也欢迎各位朋友勘误补充。实现方法及特性run ——继承QThread的run函数,通过重写run()方法,实现任务功能。使用run方便理解,简单的任务流程可以封装在run里面。run 是线程的入口,run的开始和结束意味着线程的开始和结束。多线程访问变量或处理事务要考虑加锁。(目前还未涉及加锁,不展开讨论)...

写在前头:最近在学习多线程,以小白的视角写一些学习心得。也欢迎各位朋友勘误补充。
开发环境:VS2013+QT5

实现方法及特性

  • run ——继承QThread的run函数,通过重写run()方法,实现任务功能。
  1. 使用run方便理解,简单的任务流程可以封装在run里面。
  2. run 是线程的入口,run的开始和结束意味着线程的开始和结束。
  3. 多线程访问变量或处理事务要考虑加锁。(目前还未涉及加锁,不展开讨论)
  4. 线程start()起来,理论上run函数会从头执行到结束。所以如果run中的任务是多步骤,中途需要数据反馈或ui变化,做异常处理相对麻烦。
  5. QThread只有run函数是在新线程里的,其他所有函数都在QThread生成的线程里!!!
  • moveToThread——用moveToThread将继承于QObject的类转移到Thread里。
  1. 通过信号与槽连接,几乎不用考虑多线程的存在,也不用考虑使用QMutex来进行同步。
  2. QT4.8之后,QT官方建议使用此方法。

多线程run的实现

1.代码

myThread.h文件

#pragma once
#include <QThread>

class myThread :public QThread
{
	Q_OBJECT
public:
	myThread();
	~myThread();
private:
	void run();
};

myThread.CPP文件

#include "myThread.h"
#include <QDebug>

myThread::myThread()
{
}
myThread::~myThread()
{
}
//重写run函数
void myThread::run()
{
	qDebug() << "currentThreadId is" << QThread::currentThreadId();
	//Todo此处重写功能,此处用了for循环代替。可以根据需求定义功能
	for (int i = 0; i < 100;i++)
	{
		;
	}
	qDebug() << "currentThreadId " << QThread::currentThreadId()<<"run to end";
}

RunDemo.h文件

#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_RunDemo.h"
#include "myThread.h"
#include <QtDebug>

class RunDemo : public QMainWindow
{
	Q_OBJECT
public:
	RunDemo(QWidget *parent = Q_NULLPTR);
	~RunDemo();
	
private slots:
	void on_btnOK_Clicked();

private:
	Ui::RunDemoClass ui;
	myThread *m_thread;
};

RunDemo.cpp文件

#include "RunDemo.h"

RunDemo::RunDemo(QWidget *parent): QMainWindow(parent)
{
	ui.setupUi(this);
	//实例化
	m_thread = new myThread;
	//可以在QT Designer中连接。
	connect(ui.btnOK, SIGNAL(clicked()), this, SLOT(on_btnOK_Clicked()));
}

RunDemo::~RunDemo()
{
	if (m_thread)
	{
		delete m_thread;
		m_thread = NULL;
	}
}

void RunDemo::on_btnOK_Clicked()
{
	qDebug() << "MainThreadId is" << QThread::currentThreadId();
	m_thread->start();
	ui.lineEdit->setText("hello world");
}

2.效果

子线程里做了循环操作,没有执行任何任务。lineEdit文字显示为按键槽函数执行的结果。
VS+QT多线程实现——run和moveToThread

moveToThread代码实现

1.代码

Woker.h文件

#pragma once
#include "qobject.h"
class Woker :public QObject
{
	Q_OBJECT
public:
	Woker();
	~Woker();

public slots:
    void doWork(int i);
signals:
	void SendToMain(int i);
};

Woker.cpp文件

#include "Woker.h"
#include <QDebug>
#include <QThread>

Woker::Woker()
{
}
Woker::~Woker()
{
}
//线程需要执行的任务
void Woker::doWork(int i)
{
	qDebug() << "currentThreadId is" << QThread::currentThreadId();
	//Todo此处为执行的任务功能,用了累加(+1)代替,可按需定义自己的功能
	i++;
	//发送信号
	emit SendToMain(i);
	qDebug() << "currentThreadId " << QThread::currentThreadId() << "run to end";
}

moveToThreadDemo.h文件

#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_moveToThreadDemo.h"
#include <QThread>
#include "Woker.h"
#include <QString>
#include <QDebug>

class moveToThreadDemo : public QMainWindow
{
	Q_OBJECT

public:
	moveToThreadDemo(QWidget *parent = Q_NULLPTR);

private:
	Ui::moveToThreadDemoClass ui;

signals:
	//为方便理解,这里传递的参数为int数字,可以根据需求自定义类型
	//传递的参数也可以是结构体、类对象,项目中我传递了串口(指针)
	void SendToWoker(int i);
public slots:
    void on_btnOK_Clicked();
	void handleResult(int i);
};

moveToThreadDemo.cpp文件

#include "moveToThreadDemo.h"

moveToThreadDemo::moveToThreadDemo(QWidget *parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);
	//按键功能连接
	connect(ui.btnOK, SIGNAL(clicked()), this, SLOT(on_btnOK_Clicked()));
	//声明任务woker
	Woker *myWoker = new Woker;
	//建立线程
	QThread *mythread = new QThread;
	
	connect(this, SIGNAL(SendToWoker(int)), myWoker, SLOT(doWork(int)));
	connect(myWoker, SIGNAL(SendToMain(int)), this, SLOT(handleResult(int)));
	connect(mythread, &QThread::finished, myWoker, &QObject::deleteLater);
	myWoker->moveToThread(mythread);
	mythread->start();

}
void moveToThreadDemo::on_btnOK_Clicked()
{
	qDebug() << "MainThreadId is" << QThread::currentThreadId();
	//将数字1作为信号传递出去
	emit SendToWoker(1);
}
//处理函数
void moveToThreadDemo::handleResult(int i)
{
	//接收woker传递过来的i,并进行显示
	QString qstr = QString("%1").arg(i);
	ui.lineEdit->setText(qstr);
}

2.效果

这里子线程将收到的数字1做了累加处理,并将结果传回主线程进行显示。
VS+QT多线程实现——run和moveToThread

讨论

在实际操作的时候,发现moveToThread的方法确实有其独特的优势。但在实际应用场景也出现了一个问题,希望各位朋友一起来讨论讨论。
需求是多线程多串口的开发,由于串口数量不定,所以在线程建立,连接的过程中,后面的信号也会把前面的线程中槽函数触发。可以理解为在声明的时候,加了一个for循环,这里的循环次数就是串口的数量。
VS+QT多线程实现——run和moveToThread
从运行结果来看,一共跑了6次任务。原因是第1个sendToWorker把第1个doWork(int)触发,由于是循环做的connect,第2个sendToWorker会把第1个和第2个doWork(int)触发,第3个sendToWorker会把第1个、第2个和第3个的doWork(int)触发,这就造成了系统资源的浪费。本意上是希望第i个sendToWorker只触发第i个doWork(int)。
VS+QT多线程实现——run和moveToThread

目前的做法是在woker类里,给woker加入了私有变量ID,传递参数中也增加了ID,通过判断ID是否一致来决定是否执行。但是,不清楚是不是有更好的办法。解决方案如下:
Woker.h文件

#pragma once
#include "qobject.h"
class Woker :public QObject
{
	Q_OBJECT
public:
	Woker(int ID);
	~Woker();

private:
	int _wokerID;//加入私有ID
public slots:
    void doWork(int i);
signals:
	void SendToMain(int i);
};

Woker.cpp文件

#include "Woker.h"
#include <QDebug>
#include <QThread>

Woker::Woker( int ID)
{
	_wokerID = ID;
}

Woker::~Woker()
{
}

void Woker::doWork(int i)
{
    //增加ID判断
	if (i!=_wokerID)
	{
		qDebug() << "ThreadId" << QThread::currentThreadId() << "return";
		return;
	}
	qDebug() << "currentThreadId is" << QThread::currentThreadId();
	//Todo此处重写功能,此处用了+1功能代替
	i++;
	//发送信号
	emit SendToMain(i);
	qDebug() << "currentThreadId " << QThread::currentThreadId() << "run to end";
}

VS+QT多线程实现——run和moveToThread
从结果可以看出,实际只跑了3次任务,不相干的触发信号被return,从而解决多次触发,多次执行的问题。这个方法在使用的时候也要注意,任务复位后需要disconnect。不然重复执行动作,会出现多个同ID的woker,我这里用串口号(COM3/COM4/COM5)作为woker的ID。实际场景就是串口初始化,进行通信,关闭串口需要disconnect,不然再次使用该串口,进行初始化的时候,当前连接的信号会触发前几次初始化连接的槽函数,此时ID是相同的。所以在串口关闭的时候,需要disconnect当前的连接。
VS+QT多线程实现——run和moveToThread

工程源码

  1. RunDemo下载
  2. moveToThreadDemo下载

参考资料

  1. QThread使用——关于run和movetoThread的区别
  2. Qt线程—QThread的使用–run和movetoThread的用法
  3. Qt线程实现分析-moveToThread vs 继承
  4. Qt多线程中的moveToThread()的简单用法

本文地址:https://blog.csdn.net/YRG1009/article/details/108546119