PyQt中的多线程QThread示例

该文章已生成可运行项目,

一、PyQt中的多线程

传统的图形用户界面应用程序都只有一个执行线程,并且一次只执行一个操作。如果用户从用户界面中调用一个比较耗时的操作,那么当执行这个程序时,虽然实际上该操作正在进行,但用户界面通常会冻结而不再响应。

为了解决这个问题,通常会让用户的UI界面运行在它自己的线程中,而另外的事件处理过程则发生在一个或多个其他线程中。这样做之后,即使在处理那些数据密集的事件时,应用程序也能对GUI用户界面保持响应。当在一个单处理器上运行时,多线程应用程序可能会比实现同样功能的单线程应用程序运行得更慢一些,无法体现出其优势。但在目前多处理器系统越来越普及的情况下,多线程应用程序可以在不同的处理器中同时执行多个线程,从而获得更好的总体性能。

二、创建线程

2.1 设计ui界面

我们通过下面这个小例子来展示一下多线程的处理。用户界面如下。

使用Qt designer新建一个widget窗口,然后在其中添加2个button,分别是theradA和threadB,点击btn_theradA时,会不断地在控制台打印字母A,点击btn_theradB打印字母B。保存该ui文件为Form.ui

20221208001038

界面设计完毕后,使用以下命令,将Form.ui转换为form.py

pyuic5 -o form.py Form.ui

20221208001747

2.2 设计工作线程

添加一个工作线程类,用于完成按钮按下后需要执行的任务。
MyThread.py

from PyQt5.QtCore import QThread, qDebug
import time

class MyThread(QThread):
    def __init__(self, thread_name):
        super().__init__()
        self.__stop_running = False         # 此变量用于控制线程的运行
        self.__thread_name = thread_name    # 线程名,用于区分是哪个线程

    def run(self):
        while not self.__stop_running:
            qDebug(f'{self.__thread_name} is running')  # 不停地打印,模拟执行任务
            time.sleep(1)
            
    def stop(self):
        self.__stop_running = True

在QT应用程序中提供多线程是非常简单的,只需要子类化QThread并且重新实现它的run()函数即可。

如上所述,我们定义了一个自己的Thread类,名为MyThread,它派生自QThread类,并且重新实现了run()函数。

当调用线程的start()函数时,就会自动调用run()函数。只要stop_running标志为false,run()函数就会不断地进行打印,模拟任务的执行。当控制离开run()函数时,线程就会终止。

2.3 主程序设计

程序入口文件main.py

from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtCore import qDebug
import sys
from MyThread import MyThread
from form import Ui_Form

class MyWidget(QWidget):
    def __init__(self) -> None:
        super().__init__()
        self.__init_ui()
        
        # 定义2个工作线程
        self.__threadA = MyThread('threadA')
        self.__threadB = MyThread('threadB')
        
        # 连接button的clicked信号到对应的槽函数
        self.__my_form.btn_threadA.clicked.connect(self.start_or_stop_threadA)
        self.__my_form.btn_threadB.clicked.connect(self.start_or_stop_threadB)
        
    def __init_ui(self):
        self.__my_form = Ui_Form()
        self.__my_form.setupUi(self)
        
    def start_or_stop_threadA(self):
        if self.__threadA.isRunning():
            self.__threadA.stop()
        else:
            self.__threadA.start()
        
    def start_or_stop_threadB(self):
        if self.__threadB.isRunning():
            self.__threadB.stop()
        else:
            self.__threadB.start()
            
    def closeEvent(self, e:QCloseEvent):
        qDebug('stop all threads...')
        self.__threadA.stop()
        self.__threadB.stop()
        self.__threadA.wait()
        self.__threadB.wait()
        e.accept()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    my_widget = MyWidget()
    my_widget.show()
    sys.exit(app.exec_())

主程序界面通过Ui_Form类的setupUi建立,随后我们将两个button的clicked信号绑定到我们定义的槽函数start_or_stop_threadA和start_or_stop_threadB上,这样,点击按钮之后,就可以执行我们自定义的线程动作。

在关闭窗口时,会产生QCloseEvent事件,我们重写了closeEvent方法来对线程执行了清理工作,确保应用程序是以一种原始清空的状态进行退出。当然了,在这个例子中,是否这样做其实并没有什么影响,这里只是举个例子示范一下。

三、运行结果示例

执行main.py, 单击按钮A时,控制台就会输出A,再次单击则会停止输出。按钮B同理。当按钮A和按钮B被按下同时工作时,A和B会交替输出。

运行效果如下:

20221208003621

本文章已经生成可运行项目
在使用 PyQt 开发 GUI 应用程序时,为了防止主界面因视频流读取而卡顿,通常会将相机数据的读取和处理任务放在子线程中进行。`QThread` 是 PyQt 提供的多线程模块之一,可以用来实现这一目标。结合海康威视工业相机 SDK 和 OpenCV 接口,以下是一种常见的实现方法。 ### 实现步骤 #### 1. 创建自定义线程类 继承 `QThread` 并重写其 `run()` 方法,在其中完成相机初始化、视频流读取等操作。通过信号机制将每一帧图像传递给主线程用于显示。 ```python from PyQt5.QtCore import QThread, pyqtSignal import cv2 class CameraThread(QThread): frame_ready = pyqtSignal(object) # 定义一个发送帧的信号 def __init__(self, parent=None): super().__init__(parent) self.running = True self.cap = cv2.VideoCapture() # 假设使用 OpenCV 接口连接海康相机 def run(self): while self.running: ret, frame = self.cap.read() if ret: self.frame_ready.emit(frame) # 发送帧信号 def stop(self): self.running = False ``` #### 2. 在主窗口中启动线程 在主窗口中创建并启动该线程,并将接收到的帧绘制到界面上(例如 QLabel 或 QGraphicsView)。 ```python from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QVBoxLayout from PyQt5.QtGui import QImage, QPixmap import sys class MainWindow(QWidget): def __init__(self): super().__init__() self.label = QLabel("等待视频流...") layout = QVBoxLayout() layout.addWidget(self.label) self.setLayout(layout) self.camera_thread = CameraThread() self.camera_thread.frame_ready.connect(self.update_frame) self.camera_thread.start() def update_frame(self, frame): # 将 OpenCV 的 BGR 转换为 RGB 格式 rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgb_image.shape bytes_per_line = ch * w qt_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap = QPixmap.fromImage(qt_image) self.label.setPixmap(pixmap.scaled(self.label.size())) def closeEvent(self, event): self.camera_thread.stop() self.camera_thread.wait() event.accept() ``` #### 3. 海康相机接入方式 海康威视工业相机通常支持多种接口协议,如 GigE Vision、USB3 Vision 等。对于 Python 开发者而言,可以通过以下方式接入: - **OpenCV**:部分型号可通过 OpenCV 的 `VideoCapture` 接口直接访问,示例: ```python cap = cv2.VideoCapture("rtsp://admin:password@192.168.1.108:554/Streaming/Channels/1") # RTSP 地址需根据实际情况填写 ``` - **MVS SDK + Python 绑定**:更推荐的方式是使用海康提供的 MVS SDK,通过 C/C++ 编写核心采集逻辑并通过 SWIG、Pybind11 等工具封装为 Python 模块[^1]。 #### 4. 多线程优化建议 - 使用 `moveToThread()` 替代继承 `QThread`,可更好地分离线程与业务逻辑。 - 对于高性能需求场景,可考虑使用 `QRunnable` + `QThreadPool` 构建任务队列。 - 图像传输过程中注意内存管理与深拷贝问题,避免因频繁图像传输导致性能下降。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

smart_cat

你的鼓励将是我写作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值