最近在使用QT的时候遇到不少困难,幸好主要参考这两个网站,现在都基本解决了:
在此也总结一下:
QT中的signal/slot的事件机制都是基于主程序的线程的,(辅线程中不可能使用signal,原因稍后说明),因此所有的事件都是阻塞型的,也就是说除非你处理完某个slot事件,不然不会有下个事件被触发。
但是,在此有一个例外,那就是QTimer,timerEvent(QTimerEvent
*e)事件是会重复进入的,只要时间到了,无论你之前一次的timerEvent(QTimerEvent
*e)事件是否结束,程序都会再次进入,很抱歉QTimer的代码我没有看懂,因此不知道它是怎么实现的。
其次,关于QT中用于网络通讯的类QSocket,想要使用QSocket接收数据的话,就必须connect其readyRead()事件,但是正如前
面所说的,所有
signal/slot的触发都是基于主线程的,所以你实现的slotReadyRead()代码将在主线程中被触发,如果你的程序需要接收大量的数据,
或者对数据进行一定的验证工作,(正如我现在所做的),那么把这个比较耗时的工作放在主线程中是不可行的。
那么比较容易想到的办法是将
QSocket放到线程中,但是其实这样是不可行的,(1),QThread不是QObject继承而来的,不能使用任何signal/slot机制,看
到网上有人用多重继承的方法同时从QThread和QObject继承出新的类来用,但是实践证明这样也是不可行的。(2),那么在QThread中再加
入一层从QObject继承得的类,负责connect到QSocket的readyRead()事件呢,由于之前讲过,QT的signal/slot完
全是靠主线程实现的,所以很不幸,最后你的slotReadyRead()方法最终的执行者仍然是主线程,QThread成为了摆设。。
我翻阅了QT的文档,和论坛中N人的提问,得到的结论是,如果要实现异步的数据采集功能,那么应该结合QThread和QSocketDevice两个
类,QSocketDevice不像QSocket那样,任何数据到达它都不会触发事件,相反的,你必须主动调用它的函数,查询是否有数据到达,或者干脆
调用wait函数,阻塞程序,直到有数据到达为止,对于主线程来说阻塞是不可行的,但是对于线程来说,要的就是这种效果。
那么一旦数据正常接收完毕了呢,此时数据采集线程必须主动通知主线程中的数据处理类,翻阅QT文档,找到了QThread要向主线程发送信息,必须使用
QApplication::postEvent()函数,只有这个函数才是线程安全的,于是我使用了它,但是结果却发现了另一个严重的问题,那就是
QApplication::postEvent()函数是阻塞型的,调用它的线程将等待主线程中的处理函数返回,才会继续,而我的程序中数据采集的速度
有可能大于主线程的处理速度,此时我会在线程中提供缓存功能,而不能让采集停止了,那么也就是说,我需要的通知方法必须是异步的才行。
在这里,首先想到的办法是使用QTimer,因为它是非阻塞型的,但是很抱歉,在这种时间要求比较敏感的场合,我不希望使用QTimer。
怎么办呢,我在论坛上没有找到什么有用的资料(可能是我英文比较烂),最后只能自己实现了,那就是再使用一个线程(就把它叫做事件通知线程),内部使用
QWaitCondition,让该线程始终处于等待状态,一旦数据采集线程收到数据,它就唤醒事件通知线程中的QWaitCondition,此时事件
通知线程会使用QApplication::postEvent()来向主线程发送事件。N时间以后,假设此时主线程正在处理数据,事件发送线程的
postEvent()过程尚未返回,而数据采集线程中又有数据到达,它并不会知道事件发送线程中的状态,而是简单的再次唤醒
QWaitCondition,但是作为事件发送线程,对QWaitCondition的多次唤醒将不会有任何影响,它继续阻塞在postEvent中。
又N时间后,事件发送线程中的postEvent()返回了,此时它设置自己的QWaitCondition再次进入等待状态,它将一直等待,直到下载数
据采集线程再次唤醒它。
总结一下,其实就是在事件发送线程中,如果当时正在处理postEvent(),那么它将自动忽略数据采集线程发来的任何更新命令。
而我的主线程中的处理程序在被通知时,会自动查找数据缓存中的所有数据进行处理。这样将清空所有缓存中积压的数据。就那么简单,不过我整整花了三天才逐步实现了以上的功能,希望能对大家有用。
最后,来一张最近这个程序在Linux下运行的快照:
阅读(667) | 评论(0) | 转发(1) |