QT线程: qt4和qt5使用示例

QT线程: qt4和qt5使用示例

简单介绍

QT线程: qt4和qt5使用示例

代码分析

QT4:
// 首先我们需要自定义一个线程类 MyThread
class MyThread : public QThread {
public:
    void run();	//处理数据函数 
    //必须是重载run, 我们看基类里面可以发现run是个虚函数
//添加信号
signals:
    void isDone; //数据处理完成后发送给主线程的信号
}

void MyThread::run(){
    // to do 处理数据代码
    emit isDone();	//处理完成, 发送信号给主线程, 提示可以回收资源
}

run的文档说明:
QT线程: qt4和qt5使用示例
线程的起点。在调用start()之后,新创建的线程将调用此函数。默认实现只调用exec()。
您可以重新实现此函数以方便高级线程管理。从该方法返回将结束线程的执行。
另请参见start()和wait()。

// 主线程 .h 文件中
//创建一个Mythread对象
private:
	MyThread *thread;	//创建一个线程对象thread

//添加一个回收线程资源的函数dealDone
public:
	void dealDone();	//回收资源函数, 函数说明在后面有
// 主线程 .cpp 文件中
//给thread分配内存空间
thread = new QThread(this);
//添加signal-slot
//回收资源
connect(thread, &MyThread::isDone, this, &MyWidget::dealDone);

//那么如何启动线程?
thread->start();
//这样就ok了! (示例可以把这个启动放到按钮的槽函数中触发)
QT5:
// 首先我们定义一个做数据处理的"工作类"
//这个类是继承QObject
class MyThread : public QObject{
public:
    void dealData();	//处理数据函数
signals:
    void isDone();	//处理完成, 发送信号给主线程, 提示可以回收资源
}

void MyThread::dealData(){
    // to do 处理数据代码
    emit isDone();	//处理完成, 发送信号给主线程, 提示可以回收资源
}
// 主线程 .h 文件中
//创建 工作类对象 和 子线程对象
private:
	MyThread *work;		//工作类对象
	QThread *thread;	//子线程类对象
//添加信号
signals:
    void startThread(); //开启工作的信号startThread

//添加一个回收线程资源的函数dealDone
public:
	void dealDone();	//回收资源函数, 函数说明在后面有
// 主线程 .cpp 文件中
//给"工作类对象"和"子线程类对象"分配内存空间
work = new MyThread;	//这里不能指定父对象, 后面会说明
thread = new QThread(this);

//把"工作类对象"放进"子线程对象"中
work->moveToThread(thread);	//moveToThread方法是继承QObject, 
							//将work的执行线程移动到thread线程上,
							//这也是为什么work不能指定父对象的原因.

//添加signal-slot
//开启工作
connect(this, &MyWidget::startThread, work, &MyThread::dealData);
//回收资源
connect(work, &MyThread::isDone, this, &MyWidget::dealDone);

// 那么如何启动线程?
//先启动线程 (示例可以把这个启动放到按钮的槽函数中触发)
thread->start();
//然后发送开启工作的信号
emit startThread();
//讲解:
/*	主线程发送startThread后, 我们的工作类会在子线程调用 dealData() 来处理数据,
 *	处理完数据后, dealData()会发送信号isDone,
 *	主线程接收isDone, 然后调用 dealDone() 来回收资源
 */

简单画个图你们体会下:
QT线程: qt4和qt5使用示例

QT5中自定义工作类为什么不能指定父对象?

原因是这样的, 当我们的工作类需要放在别的线程执行, 也就是使用: .moveToThread将自己的执行线程的区域移动到指定的线程中, 所以这时候我们就不能再进行指定父对象, 不然就无法使用这个方法来移动.

也可以简单理解成:
这个儿子已经有一个亲爹了, 你怎么还能把别人指定作为他的亲爹呢?

为何需要通过 signal-slot 传递?

我们有的人肯定要问了, 为什么我们创建了线程或是工作类的对象, 而且对象里也对处理数据的函数进行了定义, 为什么不直接调用函数呢, 比如直接使用 work->dealData, 其实呢, 主要原因还是在于connect之中.

我们来看看connect的文档:
[static] QMetaObject::Connection QObject::connect(const QObject sender*, const char signal, const QObject **receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)

重点看这个我划了下划线的, 可以看到, 这是connect的第五个参数, 是个枚举, 我们打开看看:

Constant Value Description
Qt::AutoConnection 0 (Default) If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.
(默认)如果接收器位于发出信号的线程中,则使用Qt::DirectConnection。否则,将使用Qt::QueuedConnection。连接类型在信号发出时确定。
Qt::DirectConnection 1 The slot is invoked immediately when the signal is emitted. The slot is executed in the signalling thread.
当信号发出时,槽函数立即被调用。槽函数在当前发送信号的线程中执行。
Qt::QueuedConnection 2 The slot is invoked when control returns to the event loop of the receiver’s thread. The slot is executed in the receiver’s thread.
当调用函数(第四个参数)返回接收者线程时,将调用槽函数。槽函数在接收者的线程中执行。
Qt::BlockingQueuedConnection 3 Same as Qt::QueuedConnection, except that the signalling thread blocks until the slot returns. This connection must not be used if the receiver lives in the signalling thread, or else the application will deadlock.
与Qt::QueuedConnection相同,只是发送信号的线程在槽函数返回之前阻塞。如果接收器位于发送信号的线程中,则必须不使用此连接,否则应用程序将死锁
Qt::UniqueConnection 0x80 This is a flag that can be combined with any one of the above connection types, using a bitwise OR. When Qt::UniqueConnection is set, QObject::connect() will fail if the connection already exists (i.e. if the same signal is already connected to the same slot for the same pair of objects). This flag was introduced in Qt 4.6.
这是一个标志,可以使用按位或与上述任何一种连接类型组合。设置Qt::UniqueConnection时,如果连接已存在(即,如果同一信号已连接到同一对对象的同一插槽),QObject::connect()将失败。这个标志是在Qt 4.6中引入的。

我们只需要注意前三个参数就可以, 第一个是默认, 也就是自动帮我们设置, 我们可以这么理解
多线程: Qt::QueuedConnection
单线程: Qt::DirectConnection
我们主要看多线程, 也就是队列连接(QueuedConnection)这个, 大概意思就是, 程序会判断我们槽函数应当在哪个线程中执行, 从而将函数的执行权交给了子线程, 这样, 我们才实现了多线程的目的.

而我们直接调用函数呢? 函数会直接在当前的线程中执行, 所以也就无法达到多线程的目的.
因此, 我们需要用到signal-slot传递, 而不能直接调用函数.

资源回收说明

void MyWidget::dealDone(){
    thread->quit();	//退出线程
    thread->wait();	//回收线程
}

资源回收简直完美?
其实并不然, 其中最大的问题出在:
thread->quit();
.quit 这个退出线程的方式是很温柔的,也就是说, 他会等待到线程结束才退出线程!

那么, 如果你的线程是个死循环, 他会不会退出了? 很显然, 是不会的!
这是个很隐秘的问题, 我们可以提出一个解决方案:

// 在自定义的线程类或工作类中
//我们可以定义个私有成员变量 isQuit, 做为标志符
bool isQuit;
//并在其构造函数中, 将其初始值设置为 false
isQuit = false;
//意味着 是否退出(isQuit), 否(false)
//然后在我们的处理数据函数中, 在死循环中添加一个条件
while(isQuit == false){ 
	//to do
}
    
//接着再在资源回收函数中将线程的isQuit设置为true
//在主线程设置子线程对象的成员变量
//你可以在自定义线程或工作类中定义个修改成员变量的函数
//然后通过signal-slot的方式, 让主线程发送信号(带参)给子线程
//然后子线程通过connect来调用修改函数, 这样就可以了.

作者声明

感谢阅读! 希望我的文章对您有帮助!
作者: tofu
QQ: 2890851110
是否可以转载: 可以, 但转载务必要署名作者和声明文章地址

上一篇:python3 +qt5,用pyqt5写一个crc校验工具,crc8 crc16 crc32 crc32


下一篇:Spring学习之路三——第一个Spring程序(体会IoC)