PySide6/PyQT多线程之 高效管理多线程:暂停、恢复和停止的最佳实践

本文通过一个示例介绍了如何使用PySide6/PyQT进行多线程编程,包括创建线程、实现线程同步、以及如何暂停、恢复和结束线程。示例中使用QThread、Signal和Slot进行线程间通信,并利用QProgressBar展示进度。此外,详细解释了线程的暂停和恢复机制,以及关键的QMutex和QWaitCondition在多线程同步中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

前言

关于 PySide6/PyQT 多线程,正确地处理多线程编程并确保线程之间的同步和通信并不容易。
本文以一个示例代码为基础,介绍 PySide6/PyQT多线程的运用,展示如何创建和管理线程,以及如何实现线程之间的同步和通信。

设想这么一个场景:

  • 在实际开发过程中,在涉及到长时间运行的计算任务时,用户可能希望能够暂停、恢复和结束线程的执行,以便更好地控制程序的行为;
  • 如线程间的同步和通信、线程的暂停、恢复和结束等;
  • ...

本专栏前面几篇文章,几乎覆盖了PySide6/PyQT 多线程编程开发中的100%,
PySide6/PyQT中, QProgressBar 控件完美适配了本文的主旨。

于是乎,这篇文章就将前面几篇 PySide6/PyQT 的文章串起来,使用QProgressBar 组件写一个进度条相关的GUI工具,方便读者更加深入的去理解多线程的使用。


值得一提的是,本文的代码是基于下面这篇文章的示例代码。


知识点📖📖

本文用到的几个PySide6的知识点及链接。

作用链接
创建新线程QThread
对象间通信的机制,允许对象发送和接收信号Signal
用于响应Signal信号的方法Slot
线程同步机制,用于协调多个线程之间对共享资源的访问QMutex
锁定互斥锁的对象,简化代码,避免手动处理锁的加锁和解锁操作QMutexLocker
线程同步机制,一般配合 QMutex 使用QWaitCondition
进度条控件QProgressBar

实现

这里是对完整代码拆解再讲解,

创建线程

  • 重写run()方法,该方法会在新线程启动时候执行;
  • 新增两个方法,分别是暂停和恢复线程
class MyThread(QThread):

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

    def pause_thread(self):
        # 在新线程中执行的暂停的代码

    def resume_thread(self):
        # 在新线程中执行的恢复的代码

    def run(self):
		# 在新线程中执行的代码

启动线程

  • 实例化类,再调用 start_thread() 启动线程
class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setup_ui()
        self.setup_thread()

    def setup_ui(self):
		# GUI界面绘制

    def setup_thread(self):
        self.thread = MyThread()

    def start_thread(self):
        self.thread.start()

    def paused_thread(self):
        self.thread.pause_thread()

    def resume_thread(self):
        self.thread.resume_thread()
        
    def stop_thread(self):
        self.thread.quit()
my_thread = MyThread()
my_thread
my_thread.start()

暂停线程

  • 调用 pause_thread() 方法,就会暂停线程
self.thread.pause_thread()

恢复线程

  • 调用 resume_thread() 方法,就会恢复线程运行
self.thread.resume_thread()

终止线程

  • 调用 quit() 方法,就会停止线程
self.thread.quit()

完整代码

该代码实现了一个具有启动、暂停、恢复和终止功能的线程,并将进度值显示在进度条上。

# -*- coding: utf-8 -*-
# Name:         demo.py
# Author:       小菜
# Date:         2023/5/4
# Description:

import sys
from PySide6.QtCore import (QThread, QWaitCondition, QMutex, Signal, QMutexLocker)
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QPushButton, QProgressBar, QApplication)


class MyThread(QThread):
    valueChange = Signal(int)

    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.is_paused = bool(0)  # 标记线程是否暂停
        self.progress_value = int(0)  # 进度值
        self.mutex = QMutex()  # 互斥锁,用于线程同步
        self.cond = QWaitCondition()  # 等待条件,用于线程暂停和恢复

    def pause_thread(self):
        with QMutexLocker(self.mutex):
            self.is_paused = True  # 设置线程为暂停状态

    def resume_thread(self):
        if self.is_paused:
            with QMutexLocker(self.mutex):
                self.is_paused = False  # 设置线程为非暂停状态
                self.cond.wakeOne()  # 唤醒一个等待的线程

    def run(self):
        while True:
            with QMutexLocker(self.mutex):
                while self.is_paused:
                    self.cond.wait(self.mutex)  # 当线程暂停时,等待条件满足
                if self.progress_value >= 100:
                    self.progress_value = 0
                    return  # 当进度值达到 100 时,重置为 0 并退出线程
                self.progress_value += 1
                self.valueChange.emit(self.progress_value)  # 发送进度值变化信号
                self.msleep(30)


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.thread_running = False  # 标记线程是否正在运行
        self.setup_ui()
        self.setup_thread()

    def setup_ui(self):
        layout = QVBoxLayout(self)
        self.progressBar = QProgressBar(self)
        layout.addWidget(self.progressBar)
        layout.addWidget(QPushButton(r'启动', self, clicked=self.start_thread))
        layout.addWidget(QPushButton(r'停止', self, clicked=self.paused_thread))
        layout.addWidget(QPushButton(r'恢复', self, clicked=self.resume_thread))
        layout.addWidget(QPushButton(r'结束', self, clicked=self.stop_thread))
        self.show()

    def setup_thread(self):
        self.thread = MyThread()
        self.thread.valueChange.connect(self.progressBar.setValue)
        self.thread_running = True

    def start_thread(self):
        if self.thread_running:
            self.thread.start()
        if not self.thread_running:
            self.setup_thread()
            self.thread.start()

    def paused_thread(self):
        if not self.thread_running:
            return
        if not self.thread.isRunning():
            self.thread.start()
        else:
            self.thread.pause_thread()

    def resume_thread(self):
        if not self.thread_running:
            return
        self.thread.resume_thread()

    def stop_thread(self):
        self.thread.quit()  # 终止线程的事件循环
        self.thread_running = False  # 标记线程停止
        self.progressBar.setValue(0)  # 重置进度条的值


if __name__ == '__main__':
    app = QApplication()
    window = MainWindow()
    sys.exit(app.exec())

代码释义

MyThread 类

MyThread类继承自QThread,是一个自定义的线程类。它通过发射信号来通知界面更新进度条的值。

属性

  • valueChange:自定义的信号,用于发送进度值变化的信号。
  • is_paused:一个布尔值,用于标记线程是否暂停。
  • progress_value:一个整数,表示进度值。
  • mutex:QMutex对象,用于线程同步。
  • cond:QWaitCondition对象,用于线程暂停和恢复。

方法

  • __init__(self, parent=None):构造函数,用于初始化对象。
  • pause_thread(self):暂停线程的方法。
  • resume_thread(self):恢复线程的方法。
  • run(self):线程执行的方法。在一个无限循环中,判断线程是否暂停,如果是则等待件满足;否则,增加进度值,并发射进度值变化的信号。
  • msleep(self, milliseconds):线程休眠的方法,以毫秒为单位。

MainWindow类

MainWindow 类是一个继承自 QWidget 的窗口类。它包含了一些状态变量和方法来管理线程和界面的交互。

属性

  • thread_running:一个布尔值,标记线程是否正在运行

方法

  • setup_ui:设置用户界面。创建一个垂直布局,并在布局中添加了一个进度条 QProgressBar 和四个按钮 QPushButton。这些按钮分别是 “启动”、“停止”、“恢复” 和 “结束”。
  • setup_thread:设置线程。实例化一个 MyThread 对象,并将进度值变化的信号 valueChange 与进度条的 setValue 方法连接起来。在线程运行时,进度条的值会随着信号的发出而更新。同时将 thread_running 标志设置为 True,表示线程正在运行。
  • start_thread:启动线程。如果 thread_running 为 True,表示线程已经存在,直接调用 start 方法来启动线程。如果 thread_running 为 False,则调用 setup_thread 方法来创建并设置线程,然后再启动线程。
  • paused_thread:用于暂停线程。如果线程没有运行,即 isRunning() 返回 False,则调用 start 方法来启动线程。否则,调用线程的 pause_thread 方法来暂停线程的执行。
  • resume_thread:用于恢复线程的执行。如果线程没有运行,即 thread_running 为 False,则直接返回。否则,调用线程的 resume_thread 方法来恢复线程的执行。
  • stop_thread:用于停止线程。它调用线程的 quit 方法来终止线程的事件循环,并将 thread_running 标志设置为 False,表示线程已停止。同时,将进度条的值重置为 0

运行结果

在这里插入图片描述

总结🎈🎈

本文中,展示了使用 PySide6/PyQT实现多线程编程,实现了一个具有启动、暂停、恢复和终止功能的线程。

本文虽然是一个简单的示例,但它也将PySide6/PyQT多线程开发中该用到的知识点都用上了,算是抛砖引玉吧。希望本文能够帮助读者理解和应用 PySide6 的多线程功能。

后话

本次分享到此结束,
see you~🐱‍🏍🐱‍🏍

PyQt中,如果在主界面线程中执行耗时操作,会导致界面卡死的问题。这是因为PyQt应用程序是基于事件驱动的,主线程负责处理GUI事件。当有耗时的操作任务时,GUI事件会被阻塞,导致应用程序处于假死状态,无法与应用程序进行交互。为了解决这个问题,可以采用多线程的方式。一种方式是继承自QThread类,将耗时的操作放在子线程中进行处理,然后通过signal-slot机制将子线程的数据反馈到主界面线程中。需要注意的是,在子线程中不能操作界面,只能处理数据。这样可以将UI的操作与耗时数据的处理进行分开处理,避免了界面卡死的问题。另一种方式是使用RunThread类继承自QObject,而非继承自QThread。这种方式将数据的处理与线程的创建与启动分开进行处理,适用于某些场景下比较方便。虽然这种方式比较复杂,但在特定情况下可以更灵活地使用多线程。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* [PyQt - 使用多线程避免界面卡顿](https://blog.csdn.net/bailang_zhizun/article/details/109240670)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [解决PySide6/PyQT的界面卡死问题(PySide6/PyQT多线程](https://blog.csdn.net/weixin_45081575/article/details/130210522)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

是小菜欸

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

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

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

打赏作者

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

抵扣说明:

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

余额充值