大家如果经常开发QT Application程序,肯定会将一些耗时的动作放到线程中,主线程再去做其它的事情,那么就会存在这样的一个问题,线程是否和主线程一样,是否可随意的调用UI?随意的创建类?鉴于这两个问题,请继续往下看。
首先说线程吧,我有一篇博客已经介绍了3中使用方式,我在这里只说信号和槽的那种,如果connect的第五个参数设为Qt::DirectConnection则槽函数处于线程中,如果是Qt::QueuedConnection则槽函数处于主线程中,如果该槽函数需要执行一些很耗时的动作,那么就必须用直连,保证在子线程中执行,这种方式就会存在一个问题,我能否访问UI,一是可以访问的,因为这个槽函数是主线程中定义的,所以它可以调用this指针,指针是关键,也就是说,想让子线程访问UI,那么就得把主Ui的指针传过去,一般是用run开启线程的话,就得弄一个全局的this指针对象,在子线程中extern声明,然后就可以访问了。队列连接,不存在该问题,因为处于主线程中,可随意访问。
我这里为啥要提QTimer QMessageBox,你们肯定遇到过这样的错误,Qtimer QMessageBox不能再另外一个线程中创建或使用,也就是说你在子线程中无法用这玩意,那么要怎么用呢,很简单,emit信号发出去,让主线程去处理。
QTimer我在提一点,它的timeout槽是处于创造它的线程中的,而且创建它和调用它的线程必须是同一个,不然就会出现不能在另外一个线程中使用,对于这个问题可通过timer::moveToThread函数来改变它的所属线程然后就可以用了,所以如果是想定时的刷新UI,并且这个刷新过程很耗时,可以采用这样的一个方式,先在主线程中开启定时器,setSingleShot设为false,表示每隔一段时间就会触发,在槽函数中,开启一个线程,然后等待线程跑完,这里可使用QEventLoop,跑完以后,关闭线程,这样即可达到线程刷新UI的目的,你们肯定会问计时器会不会没等线程跑完又触发了?我告诉你,不会,Qtimer的触发机制是上一个的槽函数运行结束,才开始算时间,触发下一次,不信你们可以去试。其实说白了这个方式就是为了开个线程,并且为这个线程绑定一个QTimer计时器,绑定的实现方式有两种,一种是自己写个类继承Qthread,然后在里面创建Qtimer,在调用Start,当Timeout时和主UI进行通讯,另外一种方式是不用继承,通过MoveToThread函数将Timer绑定到创建的线程中去,然后再通过Thread的开启来开启Timer,这样就可以,需要注意的是这里的Timeout和主线程连接需要将第五个参数改为直连,这样才能保证Timer它在子线程中,不然运行会报错,因为默认是在主线程中执行!,两种方式代码如下
1、m_timer.setSingleShot(false);
connect(&m_timer, SIGNAL(timeout()), this, SLOT(on_timer_solt()), Qt::DirectConnection);
thread = new QThread();
m_timer.moveToThread(thread);
connect(thread, SIGNAL(started()), &m_timer, SLOT(start()));
thread->start();
2、class
Worker :
public
QThread {
Q_OBJECT
public
:
Worker(MyClass *parent);
virtual
~Worker(){}
void
run();
MyClass *timerReceiver;
};
Worker::Worker(MyClass *parent)
: QThread(parent) {
timerReceiver = parent;
}
void
Worker::run()
{
QTimer *timer =
new
QTimer(
this
);
timer->setInterval(100);
connect(timer, SIGNAL(timeout()), timerReceiver, SLOT(onTimeout()), Qt::DirectConnection);
timer->start();
exec();
return
;
}
对于上面那点,还有另外一种做法,QApplication:processEvents()这个函数,其功能和开线程作用一样,将耗时的程序进入到消息循环处理,可以防止界面阻塞(卡住)
再提一个函数sleep(x)这个函数作用是让程序等待x毫秒,但是有个弊端就是,它会让UI未响应,在它的这段时间里,可以用一个事件循环来替换它,
-
QEventLoop loop;
-
QTimer::singleShot(x, &loop, SLOT(quit()));
-
loop.exec();
UI为啥会卡,其实原理就是,比如说人视频能接受的帧率FPS为60,也就是说1000ms内画面要刷新60次才不会觉得卡,放到程式里就是每隔1000/60 = 16ms刷新一次UI,当耗时超过16ms时,看上去就会卡,刷新UI的这部分可看成一个事件循环(事件循环一般都是由exec()开启,事件循环实际上类似于一个事件队列,对列入的事件依次的进行处理,程序在exec()里面无限循环,能让跟在exec()后面的代码得不到运行机会,直至程序从exec()跳出),耗时超过16ms时,此时需要立即处理一次事件循环,可使用processEvents函数,它的作用就是将耗时间的东西踢出来,自己一个去慢慢耗。让程式继续去执行其它的动作。
就说这么多了吧,这些小东西,还是有用的,面试官也很喜欢问。