QThread打造多线程应用的代码实现

嵌入式技术

1378人已加入

描述

在PyQT5中当启动通过exec()启动QApplication应用之后,就会自动开启事件循环,监听界面信号触发,当有信号触发产生之后QApplication就会调用相关的槽函数执行,这些都是在同一个线程(GUI线程)中完成。GUI线程执行长耗时任务的时,会因为没有及时返回事件轮询状态导致无法响应其它的界面动作交互,从而导致整个QApplication应用处于假死冻结状态,直到长耗时任务结束,这非常影响应用程序的用户体验。PyQT5中通过引入QThread线程作为工作线程来执行从界面触发的长耗时任务如读取与现实大文件内容、视频内容分析、批量图像处理等。

QThread线程介绍

我知道有很多神人会用QRunnable集成的方式构建一个工作者线程来实现从GUI线程调用工作线程,网上很多这么写的文章都是未经考证或者工程化实践的,我在开发OpenMV软件过程中发现根本不需要这么麻烦,直接简单粗暴用QThread可以解决的。个人认为软件开发最大的哲学不是设计模型而是 KISS(Keep It Simple and Stupid)规则,滥用设计模型跟强套一些看似高级用法是得不偿失的。   一个典型的工作者线程继承QThread类,重写run方法即可实现,骨干代码构建如下:

线程

开启一个QThread线程直接调用start()方法即可。QThread会发出 started() 与 finished()两种线程状态的信号,通过isFinished() 与isRunning() 两个方法可以直接查询线程的当前状态,调用exit()或者 quit()可以停止或者退出线程,线程执行完成之后信号finished()链接到deleteLater() 方法实现线程中对象回收与销毁。关于QThread基本知识汇总如下:完成与实现-QThread

重载run方法
  启动线程-QThread
start()
  查询状态  
isRunning() 
isFinished()
  正常结束退出  
exit()
quit()
  对象销毁
finished().connect(deleteLater())
  另外不推荐使用线程中wait()跟 sleep()方法,如果想使用sleep方法建议考虑QTimer。PyQT5中的静态方法currentThreadId()与currentThread()分别返回的是当前线程的ID与指针。

代码演示部分

演示YOLOv8模型推理,通过界面参数化设置,选择图像或者视频、选择模型文件与标签文件、设置置信阈值,然后点击开始推理按钮会自动开启一个QThread线程做推理,得到推理结果通过信号emit方式发送到界面线程,实时更新界面。实现了工作线程与界面线程分离,相互传递消息与数据。可以说会这个案例才算PyQT5开发真正的入门。开发好的界面现实如下:

线程

线程

YOLOv8推理线程代码如下:

 1class InferenceThread(QtCore.QThread):
 2    fire_stats_signal = QtCore.pyqtSignal(dict)
 3
 4    def __init__(self, settings):
 5        super(InferenceThread, self).__init__()
 6        self.settings = settings
 7        self.detector = YOLOv8Detector(settings)
 8        self.input_image = settings.input_image
 9
10    def run(self):
11        if self.detector == None:
12            return
13        if self.input_image.endswith(".mp4"):
14            cap = cv.VideoCapture(self.input_image)
15            while True:
16                ret, frame = cap.read()
17                if ret is True:
18                    self.detector.infer_image(frame)
19                    self.fire_stats_signal.emit({"result": frame})
20                else:
21                    break
22        else:
23            frame = cv.imread(self.input_image)
24            self.detector.infer_image(frame)
25            self.fire_stats_signal.emit({"result": frame})
26        self.fire_stats_signal.emit({"done": "done"})
27        return
对视频文件循环处理,每处理完成一帧,向界面线程发送处理结果,界面线程刷新显示即可。界面线程中的支持的刷新槽函数方法代码实现:  
 1def on_update_result_image(self, outs):
 2    image = outs.get("result")
 3    done = outs.get("done")
 4    if image is not None:
 5        dst = cv.cvtColor(image, cv.COLOR_BGR2RGB)
 6        height, width, channel = dst.shape
 7        bytesPerLine = 3 * width
 8        img = QtGui.QImage(dst.data, width, height, bytesPerLine, QtGui.QImage.Format_RGB888)
 9        pixmap = QtGui.QPixmap(img)
10        pix = pixmap.scaled(QtCore.QSize(800, 600), QtCore.Qt.KeepAspectRatio)
11        self.label.setPixmap(pix)
12        self.show_text("OpenCV开发者联盟-YOLOv8推理演示")
13    if done is not None:
14        self.stopBtn.setStyleSheet("background-color:gray; color: white")
15        self.stopBtn.setEnabled(False)
16        self.startBtn.setStyleSheet("background-color:cyan; color: black")
17        self.startBtn.setEnabled(True)
18
19def show_text(self, text):
20    painter = QtGui.QPainter(self.label.pixmap())
21    pen = QtGui.QPen(QtCore.Qt.green)
22    painter.setPen(pen)
23    font = QtGui.QFont()
24    font.setBold(True)
25    font.setPointSizeF(16)
26    painter.setFont(font)
27    painter.drawText(QtCore.QPoint(10, 200), text)
28    painter.end()

编辑:黄飞

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分