通过以上的实例可以看到,我们无需重写 QThread::run 函数,也无需显式调用 QThread::exec 来启动线程的事件循环了,通过QT源码可以知道,只要调用 QThread::start 它就会自动执行 QThread::exec 来启动线程的事件循环。
子类化QThread实现多线程的创建方法,如果run函数里面没有死循环也没有调用exec开启事件循环的话,就算调用了 QThread::start 启动线程,最终过一段时间,线程依旧是会退出,处于finished的状态。那么这种方式会出现这样的情况吗?我们直接运行上面的实例,然后过段时间检查线程的状态:
发现线程是一直处于运行状态的。那接下来我们说一下应该怎么正确使用这种方式创建的线程并正确退出线程和释放资源。
三、如何正确使用线程(信号槽)和创建线程资源(1)如何正确使用线程?
如果需要让线程去执行一些行为,那就必须要正确使用信号槽的机制来触发槽函数,其他的方式调用槽函数都只是在旧线程中执行,无法达到预想效果。在多线程中信号槽的细节,会在后期的《跨线程的信号槽》文章来讲解,这里我们先简单说如何使用信号槽来触发线程执行任务先。
通过以上的实例得知,MainWindow 构造函数中使用了connect函数将 StartTimerSignal() 信号和 InheritQObject::TimerSlot() 槽进行了绑定,代码语句如下:
当点击【startTime】按钮发出 StartTimerSignal() 信号时,这个时候就会触发线程去执行 InheritQObject::TimerSlot() 槽函数进行计时。
由上面的打印信息得知,InheritQObject::TimerSlot() 槽函数的确是在一个新的线程中执行了。在上面继承QThread的多线程方法中也有说到,在这个时候去执行QThread::exit或者是QThread::quit是无效的,退出的信号会一直挂在消息队列里,只有点击了【stopTime】按钮让线程退出 while 循环,并且线程进入到事件循环 ( exec() ) 中,才会生效,并退出线程。
如果将【startTime】按钮不是发出 StartTimerSignal() 信号,而是直接执行InheritQObject::TimerSlot() 槽函数,会是怎么样的结果呢?代码修改如下:
//触发线程执行m_obj的计时槽函数 void on_startBt_clicked(){ m_obj->TimerSlot(); }我们会发现界面已经卡死,InheritQObject::TimerSlot() 槽函数是在GUI主线程执行的,这就导致了GUI界面的事件循环无法执行,也就是界面无法被更新了,所以出现了卡死的现象。所以要使用信号槽的方式来触发线程工作才是有效的,不能够直接调用obj里面的成员函数。
(2)如何正确创建线程资源?
有一些资源我们可以直接在旧线程中创建(也就是不通过信号槽启动线程来创建资源),在新线程也可以直接使用,例如实例中的bool m_flag和QMutex m_lock变量都是在就线程中定义的,在新线程也可以使用。但是有一些资源,如果你需要在新线程中使用,那么就必须要在新线程创建,例如定时器、网络套接字等,下面以定时器作为例子,代码按照下面修改:
/**********在InheritQObject类中添加QTimer *m_timer成员变量*****/ QTimer *m_timer; /**********在InheritQObject构造函数创建QTimer实例*****/ m_timer = new QTimer(); /**********在InheritQObject::TimerSlot函数使用m_timer*****/ m_timer->start(1000);运行点击【startTime】按钮的时候,会出现以下报错:
QObject::startTimer: Timers cannot be started from another thread