您的位置:

PyQt5 QThread 的多个方面详解

一、QThread 的背景和概念

Python语言下的GUI编程常用的库是PyQt5,而其中的QThread则是经常用到的一个类。QThread是封装了线程(thread)的Qt库中的类。线程是我们常说的一个程序中的独立流程,它是一个包含指令序列的执行单元,是一个进程内部的一个相对独立的、可调度的执行单元。有了线程可以实现多任务并行执行,提高程序的执行效率。但实现多线程程序不是一件容易的事情,有许多难以解决的问题,而PyQt5中的QThread类就提供了一个相对较简单的线程实现方式。

在使用QThread之前,需要了解一些与多线程有关的概念,包括:sleep、信号(Signal)和槽(Slot)等概念。sleep是使线程进入睡眠状态,暂停执行一段时间后重新执行。信号和槽是在不同线程间通讯的机制,其中信号是在发生某个特定事件时发送的一种消息,而槽则是由线程处理该消息并执行相应的动作。


import time, threading

def func():
    for i in range(5):
        time.sleep(1)
        print('func executed')

t = threading.Thread(target=func)
t.start()

二、QThread 的基本用法

QThread类的应用可以分为两步:第一步是创建QThread对象,第二步是在QThread对象中创建一个自定义的类继承QThread,并重写其run方法。run方法就是我们想要在线程中执行的代码。在自定义类中可以使用信号和槽机制与主线程(或其他线程)交互。

下面是一个简单的QThread使用示例,先看代码:


import sys
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget, QLabel


class Worker(QThread):
    signal_output = pyqtSignal(str)

    def __init__(self, parent=None):
        super(Worker, self).__init__(parent)

    def run(self):
        for i in range(3):
            self.signal_output.emit(f"Worker thread output {i}")
            self.sleep(1)

class MainWidget(QWidget):
    def __init__(self, parent=None):
        super(MainWidget, self).__init__(parent)
        self.label = QLabel(self)
        self.worker = Worker(self)

        self.worker.signal_output.connect(self.handle_output)
        self.worker.start()

    def handle_output(self, msg):
        self.label.setText(msg)


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

这段代码的功能是创建一个主线程的QWidget窗口,其中包含一个QLabel标签。在程序运行时,会启动一个子线程(即Worker类),每隔一秒发送一个信号并emit(激活)一个自定义的信号signal_output,发送内容是字符串“Worker thread output i”,其中 i 从 0 开始每次加 1。而界面则会在接收到信号后把字符串显示到QLabel标签上。

三、QThread 的高级应用

除了基本的使用外,QThread还有一些高级用法,例如如何优雅地停止线程、如何在线程中使用QTimer、QThreadPool等等。

下面是一个使用QTimer的例子,在子线程中每隔1秒将数据+1并输出,同时还有一个按下按钮可以暂停线程:


import sys
from PyQt5.QtCore import QThread, QTimer, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout, QPushButton


class Worker(QThread):
    signal_output = pyqtSignal(int)

    def __init__(self, parent=None):
        super(Worker, self).__init__(parent)
        self.timer = QTimer(self)
        self.timer.timeout.connect(self._process_data)
        self.iteration = 0
        self.is_running = True

    def _process_data(self):
        self.iteration += 1
        self.signal_output.emit(self.iteration)

    def run(self):
        self.timer.start(1000)

        while self.is_running:
            self.sleep(1)

        self.timer.stop()

    def stop(self):
        self.is_running = False


class MainWidget(QWidget):
    def __init__(self, parent=None):
        super(MainWidget, self).__init__(parent)
        self.label = QLabel(self)
        self.button = QPushButton("Stop", self)

        self.layout = QVBoxLayout(self)
        self.layout.addWidget(self.label)
        self.layout.addWidget(self.button)

        self.worker = Worker(self)

        self.worker.signal_output.connect(self.handle_output)
        self.button.clicked.connect(self.worker.stop)

        self.worker.start()

    @pyqtSlot(int)
    def handle_output(self, value):
        self.label.setText(f"Worker thread output {value}")


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

这段代码采用QTimer作为定时器,每隔1秒触发一次_process_data函数,将iteration的数值加1,并将数值emit到自定义的信号signal_output中。同时,在外部有一个按钮可以调用worker的stop方法,以便优雅地停止线程。

四、总结

QThread是PyQt5库中常用的多线程编程类,可以轻松地实现多线程效果。在应用QThread时需要注意一些线程概念和信号槽机制,并且如果需要更高级的应用(例如使用QTimer、QThreadPool等)则需要对QThread类更加深入地了解。