PySide6/PyQT多线程之 编程入门指南:基础概念和最佳实践

举报
frica01 发表于 2023/10/31 20:48:01 2023/10/31
【摘要】 在使用PySide6/PyQT中多线程的基本概念以及QThread的注意事项和基本使用!nice~~

前言

本篇文章介绍 PySide6/PyQT多线程编程的基本概念,用到的知识点,以及PySide6/PyQT多线程的基本使用。

看多线程介绍,就看 知识点📖📖

看多线程代码,就看 实现


知识点📖📖

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

作用 链接
创建新线程 QThread
对象间通信的机制,允许对象发送和接收信号 Signal
用于响应Signal信号的方法 Slot
互斥锁,用于保护对象 QMutex
QMutex一起使用 QWaitCondition

多线程模块

值得注意的是在 PySide6/PyQT 中实现多线程,可以选择的有很多,有 QObject、QThread、QRunnable、QThreadPool 等;

  • 其中QThread是继承了QObject 的类,它可以作为基类来创建自定义的线程类;
  • QThreadPool 是一个线程池类,它管理着一组线程,可以方便地调度和管理线程的执行;
  • QRunnable 是一个任务接口,用于将任务封装为一个可在后台线程中执行的对象。可以通过继承 QRunnable 类来实现自定义的任务,并在 QThreadPool 中执行。

多线程通信和同步

在多线程的编程中,线程之间的通信和同步是绕不开的话题。

通信:

  • PySide6/PyQT 提供了信号槽(Signal and Slot) 机制,它们用于在线程之间传递消息和触发事件。通过在不同的线程中发送信号和连接槽函数,可以实现线程间的通信。

同步:

  • 在共享对象被多个线程同时访问时候容易出现意料之外的问题,需要保护好资源争夺;
  • 互斥锁(QMutex)和条件变量(QWaitCondition)等同步机制可以用于控制线程的并发访问,确保线程安全和避免线程竞争。

多线程异常

这个较为通俗易懂,一般来说 使用 try-except 去捕获线程内部可能发生的异常,并在 except 语句块中处理异常即可。

异常处理:

  • 在捕获到异常后,可以选择重新尝试操作、回退到安全状态、释放资源、打印错误信息等。

当然,也可以在异常时候 使用signal信号发送异常的消息。(示例代码如下

from PySide6.QtCore import QThread, Signal

class MyThread(QThread):
    # 定义一个线程崩溃的信号
    thread_crashed = Signal(Exception)

    def run(self):
        try:
            # 线程的执行逻辑
            pass
        except Exception as e:
            # 发送线程崩溃的信号
            self.thread_crashed.emit(e)

多线程生命周期

线程的生命周期包括:线程的创建、启动、运行、暂停、恢复和终止等阶段。

在使用多线程时候,需要注意多线程的生命周期。因为合理管理线程的生命周期可以避免线程资源的浪费和泄漏。

  • 如果是使用QThread,则需要手动处理这些操作,否则可能会导致资源泄漏或线程的未正常退出。

  • 如果是使用 QRunnbale + QThreadPool,主线程则会根据线程池的设置自动创建和销毁线程,从而减少了手动管理线程的复杂性。

所以我推荐使用 QRunnbale + QThreadPool,因为省事~~


多线程优先级

线程优先级决定了线程在竞争CPU资源时的执行顺序,优先级越高的线程在竞争CPU时会被更早地执行。

当然,这个线程优先级并不一定保证绝对的执行顺序,只是优先级越高的在竞争CPU时候,会有更高的概率更早执行;因为线程优先级的具体表现会受到系统的调度算法和资源情况的影响。


多线程应用场景

如果你看了这篇文章,那么大概你已经有了 多线程的应用场景了。

大致搜罗了下,应用场景如下:

  1. 后台下载文件、图像处理,数据处理等 避免阻塞主线程,提升处理的性能和用户体验。
  2. 网络请求网络通信等;
  3. 多任务并发执行;
  4. 反正一句话!就是避免阻塞主线程并提高效率!!!

实现

QThread 多线程

使用注意事项

在使用 PySide6QThread 时,注意事项包括且不局限于以下几点:

  • 不要使用Python原生的线程库来实现;
  • 消息或任务结果 通过Signal信号来传递;
  • 不要在QThread调用主线程的GUI控件应用程序会进入卡死状态;
  • QThread对象必须在主线程中创建,否则程序可能会奔溃;
    • 因为PySide6是基于事件循环的框架,GUI线程子线程都运行在同一个事件循环中。如果在子线程中创建和启动QThread对象,它会尝试创建一个新的事件循环,这会导致两个事件循环并行运行,产生无法预估的结果。

代码

代码参考这一篇文章中的,看这里 —《解决PySide6/PyQT的界面卡死问题(PySide6/PyQT多线程》

# -*- coding: utf-8 -*-

import time

import requests

from PySide6.QtCore import (QThread, Signal, Slot, QSize)
from PySide6.QtWidgets import (QApplication, QPushButton, QLabel, QVBoxLayout, QWidget)


class MyThread(QThread):
    signal_tuple = Signal(tuple)

    def __init__(self, func, *args, **kwargs):
        super().__init__()
        self.func = func
        self.args = args
        self.count: int = kwargs.get('count')

    def run(self):
        for idx in range(1, self.count + 1):
            result = self.func(*self.args)
            time.sleep(1)
            # 任务完成后发出信号
            self.signal_tuple.emit((idx, result))


class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.setup_ui()
        #
        self.button.clicked.connect(self.setup_thread)

    def setup_ui(self):
        self.setWindowTitle('demo')
        self.resize(QSize(250, 180))
        # 创建一个垂直布局
        layout = QVBoxLayout()
        # 创建一个标签
        self.label = QLabel('This is a label => ')
        layout.addWidget(self.label)
        # 创建一个按钮
        self.button = QPushButton('Send Request')
        layout.addWidget(self.button)
        # 将布局设置为主窗口的布局
        self.setLayout(layout)
        # 显示窗口
        self.show()

    def setup_thread(self):
        self.thread_ = MyThread(self.send_request,
                                count=10)
        self.thread_.signal_tuple.connect(self.thread_finished)
        self.thread_.start()

    def send_request(self):
        return requests.get('https://bbs.huaweicloud.com/community/usersnew/id_1581837467993674').text[:15]

    @Slot(tuple)
    def thread_finished(self, item):
        self.label.setText('This is a label => ' + str(item))


if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()

代码释义


MyTherad类

  • 继承了QThread
  • 创建了一个 signal_tuple信号;
  • 类接收一个func函数以及不定长的传参(这里主要传funccount
  • 重写 run方法,线程的 start() 方法被调用时,就会自动执行run方法;
  • 执行 count 次数的func,每次睡眠1秒,使用signal_tuple 将执行结果和执行次数发送出去。

MainWindow类

  • 继承了 QWidget,实现了包含一个按钮和一个标签的简单窗口;
  • setup_thread 函数中,实例化了MyThread,并将实例化后的signal_tuple信号连接到 thread_finished函数;
  • thread_finished为槽函数,当signal_tuple发出信号时,这Slot(槽函数)将被调用,并修改label 的显示。

在实例代码 以及 代码释义中可以清晰的看到,示例代码完美符合了本文中我指出来的 QThread 多线程的几个注意事项:

  • 使用Signal信号传递运行结果;
  • 在主线程中创新子线程;
  • 不在子线程中修改主线程的控件。

以上便是 QThread 多线程PySide6/PyQT 中的简单使用。


后话

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。