PyQt入门(3)-多线程

目录

一、QThreadPool+QRunnable

二、子类化QThread

三、QObject.moveToThread+QThread


PyQt5不支持Qt Concurrent模块,所以PyQt5多线程的实现方式是以下三种:

QThreadPool+QRunnable(详细介绍)

子类化QThread(详细介绍)

QObject.moveToThread+QThread(详细介绍)

下面通过这三种方式实现一个同样功能的计时器界面程序。

一、QThreadPool+QRunnable

QThreadPool(线程池)管理和回收多个QRunnable对象,以帮助降低线程创建成本。每个Qt应用程序都有一个全局QThreadPool对象,可以通过调用QThreadPool.globalInstance()来访问该象。

要使用QThreadPool执行线程,请将QRunnable子类化并实现run()函数,然后创建该类的对象,并将其传递给QThreadPool的start()。

默认情况下,QThreadPool会自动删除执行完的QRunnable。可以使用QRunnable的setAutoDelete()更改自动删除标志。

在一定时间内未使用的线程将过期。默认的过期超时为30000毫秒(30秒)。这可以使用setExpiryTimeout()进行更改。设置负到期超时将禁用到期机制。

调用maxThreadCount()查询要使用的最大线程数。如果需要,可以使用setMaxThreadCount()更改限制。默认的最大线程计数是QThread.idealThreadCount()。activeThreadCount()函数返回当前正在执行工作的线程数。

QRunnable没有继承QObject,所以在QRunnable的子类中是无法定义信号的

import time
import sys
from PyQt5.QtCore import QThreadPool, QRunnable, QObject, pyqtSignal
from PyQt5.QtWidgets import QMainWindow, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QLineEdit, QLabel, QApplication

class Signals(QObject):
    # 将信号统一定义在一个QObject子类中
    signal_timer_end = pyqtSignal()
    signal_timer_cur_time = pyqtSignal(int)

# 实例化Signals以便使用信号,千万不能直接以Signals.signal_timer_end 的方式使用信号
SignalInstance = Signals()

class TimerThread(QRunnable):
    # 继承QRunnable实现一个线程类TimerThread
    # TimerThread里是不能定义信号的
    def __init__(self):
        super().__init__()
        # 外部无法控制QThreadPool里的线程,所以只能通过定义标志位的方式
        self.stop = False
        self.cur_time = 0
        SignalInstance.signal_timer_end.connect(self.stop_timer)

    def stop_timer(self):
        # 控制线程结束
        self.stop = True

    def run(self) -> None:
        # 一旦结束while循环,退出run,QThreadPool就会自动销毁TimerThread
        while not self.stop:
            SignalInstance.signal_timer_cur_time.emit(self.cur_time)
            time.sleep(0.5)
            self.cur_time += 1


class DemoWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("测试窗口1")
        self.resize(700, 500)

        self.setCentralWidget(QWidget())
        self.main_layout = QVBoxLayout()
        self.centralWidget().setLayout(self.main_layout)

        self.main_layout.addWidget(QLineEdit())

        self.header_layout = QHBoxLayout()

        self.btn_timer_start = QPushButton("扫描")
        self.btn_timer_start.clicked.connect(self.timer_start)
        self.header_layout.addWidget(self.btn_timer_start)

        self.btn_timer_stop = QPushButton("停止")
        self.btn_timer_stop.clicked.connect(self.timer_stop)
        self.header_layout.addWidget(self.btn_timer_stop)
        self.main_layout.addLayout(self.header_layout)

        self.l_timer = QLabel()
        self.main_layout.addWidget(self.l_timer)

        SignalInstance.signal_timer_cur_time.connect(self.set_cur_time)

    def closeEvent(self, a0) -> None:
        SignalInstance.signal_timer_end.emit()

    def set_cur_time(self, cur_time):
        self.l_timer.setText(str(cur_time))

    @classmethod
    def timer_start(cls):
        # 获取全局的QThreadPool对象执行线程
        QThreadPool.globalInstance().start(TimerThread())

    @classmethod
    def timer_stop(cls):
        SignalInstance.signal_timer_end.emit()

if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = DemoWindow()
    window.show()

    sys.exit(app.exec_())

二、子类化QThread

QThread对象管理程序中的一个线程。QThread在run()中开始执行。默认情况下,run()通过调用exec()启动事件循环,并在线程内运行Qt事件循环。QThread通过start()启动。

QThread的实例化相关代码还是在旧线程中执行的,run()是在新线程中执行的

当线程启动和结束时,QThread会通过信号startedfinished通知您,或者您可以使用isFinished()和isRunning()来查询线程的状态。

您可以通过调用exit()或quit()来停止线程。在极端情况下,您可以调用terminate()来强制终止一个正在执行的线程。然而,这样做是危险的,也是令人沮丧的。(但是很多时候exit和quit根本不好使,还是terminate好使)

使用wait()会阻塞调用线程,直到线程完成执行(或直到指定的时间过去)。wait()函数通常是不必要的,因为Qt是一个事件驱动的框架。与其wait(),不如考虑监听finished信号。

QThread还提供与平台无关的睡眠函数:sleep()、msleep()和usleep()分别为秒、毫秒和微秒级。

QThread.currentThreadId()和QThread.currentThread()返回当前执行线程的标识符。前者返回线程的特定于平台的ID;后者返回一个QThread指针。

import time
import sys
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QMainWindow, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QLineEdit, QLabel, QApplication

class TimerThread(QThread):
    # 子类化一个QThread
    # QThread继承自QObject,是可以定义信号的
    signal_timer_cur_time = pyqtSignal(int)

    def __init__(self):
        super().__init__()
        self.cur_time = 0

    def run(self) -> None:
        self.cur_time = 0
        # QThread可以通过terminate强制终止,这里就没用标志位控制退出了
        while True:
            self.signal_timer_cur_time.emit(self.cur_time)
            time.sleep(0.3)
            self.cur_time += 1

class DemoWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.setWindowTitle("测试窗口3")
        self.resize(700, 500)

        self.setCentralWidget(QWidget())
        self.main_layout = QVBoxLayout()
        self.centralWidget().setLayout(self.main_layout)

        self.main_layout.addWidget(QLineEdit())

        self.header_layout = QHBoxLayout()

        self.btn_timer_start = QPushButton("扫描")
        self.btn_timer_start.clicked.connect(self.timer_start)
        self.header_layout.addWidget(self.btn_timer_start)

        self.btn_timer_stop = QPushButton("停止")
        self.btn_timer_stop.clicked.connect(self.timer_stop)
        self.header_layout.addWidget(self.btn_timer_stop)
        self.main_layout.addLayout(self.header_layout)

        self.l_timer = QLabel()
        self.main_layout.addWidget(self.l_timer)

        # 实例化一个TimerThread
        self.timer_thread = TimerThread()
        self.timer_thread.signal_timer_cur_time.connect(self.set_cur_time)

    def closeEvent(self, a0) -> None:
        self.timer_stop()

    def set_cur_time(self, cur_time):
        self.l_timer.setText(str(cur_time))

    def timer_start(self):
        self.timer_thread.start()

    def timer_stop(self):
        # 官网说terminate很危险,我却觉得很好使啊,当然了,如果真的觉得危险
        # 也可以像上面一样通过标志位控制退出
        self.timer_thread.terminate()

if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = DemoWindow()
    window.show()

    sys.exit(app.exec_())

三、QObject.moveToThread+QThread

这种方式是子类化QObject,将业务逻辑放在这个子类中,然后通过moveToThread把QThread和这个子类需要在新线程中执行的方法连接起来,在新线程中执行这个方法,具体看代码:

import time
import sys
from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import QMainWindow, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QLineEdit, QLabel, QApplication


class Worker(QObject):
    # Worker 子类化QObject

    signal_timer_cur_time = pyqtSignal(int)

    def __init__(self):
        super().__init__()
        self.cur_time = 0
        self.stop = False

    def stop_worker(self):
        # 通过标志位控制停止worker
        self.stop = True

    def run(self) -> None:
        self.cur_time = 0
        if self.stop:
            self.stop = False
        while not self.stop:
            self.signal_timer_cur_time.emit(self.cur_time)
            time.sleep(0.1)
            self.cur_time += 1


class DemoWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.setWindowTitle("测试窗口5")
        self.resize(700, 500)

        self.setCentralWidget(QWidget())
        self.main_layout = QVBoxLayout()
        self.centralWidget().setLayout(self.main_layout)

        self.main_layout.addWidget(QLineEdit())

        self.header_layout = QHBoxLayout()

        self.btn_timer_start = QPushButton("扫描")
        self.btn_timer_start.clicked.connect(self.timer_start)
        self.header_layout.addWidget(self.btn_timer_start)

        self.btn_timer_stop = QPushButton("停止")
        self.btn_timer_stop.clicked.connect(self.timer_stop)
        self.header_layout.addWidget(self.btn_timer_stop)
        self.main_layout.addLayout(self.header_layout)

        self.l_timer = QLabel()
        self.main_layout.addWidget(self.l_timer)

        # QThread不需要子类化了,直接实例化
        self.timer_thread = QThread()
        # 实例化Worker
        self.worker = Worker()
        # 指定worker在timer_thread中执行
        self.worker.moveToThread(self.timer_thread)
        # 把timer_thread的started信号和worker的run连接起来,这样调用timer_thread.start()
        # 就会自动执行worker.run()
        self.timer_thread.started.connect(self.worker.run)
        self.worker.signal_timer_cur_time.connect(self.set_cur_time)

    def closeEvent(self, a0) -> None:
        self.worker.stop_worker()
        self.timer_thread.terminate()
        self.timer_thread.wait()

    def set_cur_time(self, cur_time):
        self.l_timer.setText(str(cur_time))

    def timer_start(self):
        # 启动timer_thread,worker.run()也开始执行
        self.timer_thread.start()

    def timer_stop(self):
        # 停止worker
        self.worker.stop_worker()
        # 这里quit可以生效是因为run已经退出了,如果run里面还是while True的方式,那quit是无法启作用的
        self.timer_thread.quit()

if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = DemoWindow()
    window.show()

    sys.exit(app.exec_())

上面的代码可以看出来,worker的run()要执行只能在timer_thread的start()被调用的时候自动执行,这样有点不灵活,有时候我们可能希望timer_thread的start()和worker的run()是分开的,可以通过信号来实现:

import time
import sys
from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import QMainWindow, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QLineEdit, QLabel, QApplication

class Worker(QObject):
    # Worker 子类化QObject

    signal_timer_cur_time = pyqtSignal(int)

    def __init__(self):
        super().__init__()
        self.cur_time = 0
        self.stop = False

    def stop_worker(self):
        # 通过标志位控制停止worker
        self.stop = True

    def run(self) -> None:
        self.cur_time = 0
        if self.stop:
            self.stop = False
        while not self.stop:
            self.signal_timer_cur_time.emit(self.cur_time)
            time.sleep(0.1)
            self.cur_time += 1


class DemoWindow(QMainWindow):

    signal_start_worker = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.setWindowTitle("测试窗口4")
        self.resize(700, 500)

        self.setCentralWidget(QWidget())
        self.main_layout = QVBoxLayout()
        self.centralWidget().setLayout(self.main_layout)

        self.main_layout.addWidget(QLineEdit())

        self.header_layout = QHBoxLayout()

        self.btn_timer_start = QPushButton("扫描")
        self.btn_timer_start.clicked.connect(self.timer_start)
        self.header_layout.addWidget(self.btn_timer_start)

        self.btn_timer_stop = QPushButton("停止")
        self.btn_timer_stop.clicked.connect(self.timer_stop)
        self.header_layout.addWidget(self.btn_timer_stop)
        self.main_layout.addLayout(self.header_layout)

        self.l_timer = QLabel()
        self.main_layout.addWidget(self.l_timer)

        # QThread不需要子类化了,直接实例化
        self.timer_thread = QThread()
        # 直接启动timer_thread线程
        self.timer_thread.start()
        # 实例化Worker
        self.worker = Worker()
        # 指定worker在timer_thread中执行
        self.worker.moveToThread(self.timer_thread)
        # 把signal_start_worker连接到worker.run
        self.signal_start_worker.connect(self.worker.run)
        self.worker.signal_timer_cur_time.connect(self.set_cur_time)

    def closeEvent(self, a0) -> None:
        self.worker.stop_worker()
        self.timer_thread.terminate()
        self.timer_thread.wait()

    def set_cur_time(self, cur_time):
        self.l_timer.setText(str(cur_time))

    def timer_start(self):
        if not self.timer_thread.isRunning():
            self.timer_thread.start()
        # signal_start_worker信号只要一emit,worker.run就会执行
        self.signal_start_worker.emit()

    def timer_stop(self):
        # 停止worker
        self.worker.stop_worker()
        # 这里就不要quit了,否则timer_thread不是运行状态,在signal_start_worker信号emit之前就需要重新启动timer_thread
        # self.timer_thread.quit()

if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = DemoWindow()
    window.show()

    sys.exit(app.exec_())

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值