QT同时实现通信、界面交互总结

2019-04-15 16:58发布

很长时间没写总结了,最近事情太多,加上错估了slab算法的难度,导致一直没搞定这个内存的核心分配算法,只能一拖再拖了。前段时间一直在用QT做一个单片机升级任务,其中遇到问题比较多,现在完成后正好可以总结下两个和QT有关的。
基本流程:上位机是ARM,下位机是CAN128单片机,两者利用CAN作为通信接口。ARM端把单片机的bin升级文件发送给单片机端,单片机进行自升级操作。ARM端在升级过程中除了传输还需要以界面的形式显示升级的流程。CAN通信的流程不再说,途中遇到了两个和QT相关的问题:界面刷新和QTimer在while循环中失效问题。 1.while循环中界面刷新失效,最开始我的代码结构是这样的
初始化界面(显示界面、进度条界面) { progress->show(); //先把进度条界面显示出来 while(1) { 从文件读取一帧数据 通过CAN发送 校验发送成功后对进度条界面进行更新(下面语句) progress->setProgressValue(value); //progress是一个QWidget类型,setProgressValue其实就是封装了一个设置界面文字的方法 } } 结果发现数据能正常传输,但是进度条界面没有显示,而是到最后升级完毕后才显示最终跑到100%的界面。问题原因:QT主函数在执行时候流程如下: int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; //构造界面 w.show(); //显示界面 return a.exec(); }
而当程序执行到show函数时候,实际只是显示了窗口的载体,并没有显示窗口上的任何内容,这个可以在show语句下加个死循环就可以看出来。而向界面中setText等操作,必须等到a.exec()语句执行后才能显示。而这个函数其实是调用的qApp->processEvents()实现。函数原型如下: void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents) [static]
再看下帮助文档相关解释:Processes all pending events for the calling thread according to the specified flags until there are no more events to process.   意思就是对于调用这个接口的线程,会处理所有的等待事件直到这个线程没有事件需要处理了。对于单线程程序,界面响应就是等待事件,所以解决方法就是在每一句界面发生变化需要刷新的时候加上这么一句话,那么代码就变成了下面的结构: { progress->show(); //先把进度条界面显示出来 while(1) { 从文件读取一帧数据 通过CAN发送 校验发送成功后对进度条界面进行更新(下面语句) progress->setProgressValue(value); //progress是一个QWidget类型,setProgressValue其实就是封装了一个设置界面文字的方法 qApp->processEvents(); } }这样就解决了上述问题,进度条随着传输进度而更新前进。
2.while循环中定时器失效问题 现在加入这么一个需求:在发送每一帧后进行计时操作,当长时间未收到来自单片机的回复后判定为连接故障,于是代码结构变成:
{ QTimer *timer = new QTimer; progress->show(); //先把进度条界面显示出来 while(1) { 从文件读取一帧数据 通过CAN发送一帧数据 timer->start(3000); //启动定时器,3秒后定时器会发送超时信号 校验发送成功后对进度条界面进行更新(下面语句) progress->setProgressValue(value); //progress是一个QWidget类型,其实就是一般的更新界面 qApp->processEvents(); } } 结果发现定时器失效了,到了3s并没有发出timeout信号,结合上面问题我们就可以分析出来,当只有一个线程的进程在运行时候,当线程进入while循环后,会霸占住CPU,而QTimer就没有机会发出超时信号甚至根本没有机会去执行,这个我没有想出好的办法去验证到底是哪种情况。而从加入了qApp->processEvents()语句后仍然生效来看,应该是在while中没有机会执行,因为即使加上这句话能够执行一次计时,循环的次数也不足以让超时条件满足。可能这几句话说的比较绕,不理解也没关系,只需要知道即使用问题1的解决方案,也无法让定时器正常工作。那唯一的办法只能用多线程来实现了,于是代码变成了如下结构: 把整个任务分到两个线程,A线程只管界面变化,B线程只管通信传输,按照C++语法实现就是两个文件了: A文件(界面文件)结构: { 各个界面初始化、布局 progress->show(); //先把进度条界面显示出来 QThread bThread; //初始化B线程 bThread.start(); //启动B线程 连接线程的定时器超时信号--做某个处理 连接线程的帧发送信号--供进度条使用 其它线程信号连接 } B文件(通信线程)结构: class SystemUpdateThread : public QThread(从线程继承过来) { while(1) { 从文件读取一帧数据 通过CAN发送一帧数据 timer->start(3000); //启动定时器,3秒后定时器会向界面发送超时信号 校验发送成功后发送一帧成功的信号(下面语句) emit oneFrameSend(index); //把这个信号发给界面,index是界面进度条需要显示的值 } } 改为以上结构后,不仅很好解决了定时器问题,而且第一个问题也不再需要调用processEvents()接口,并且界面和通信分开,代码层次更加分明,容易维护。所以,QT中通信相关处理按这个流程走,应该是问题最少的了。