解决PySide6/PyQT的界面卡死问题(PySide6/PyQT多线程

文章介绍了在使用PySide6时遇到应用程序卡死问题的原因,主要是由于主线程执行耗时任务导致GUI事件阻塞。为了解决这个问题,文章提出了将长时间运行的任务移到单独线程中执行,特别是通过QThread实现多线程,并利用信号和槽机制传递结果。示例代码展示了如何创建QThread子类,以及如何在主线程和工作线程间通信,从而保证应用程序的响应性。
摘要由CSDN通过智能技术生成

前言

问:在使用 PySide6 时候,会出现应用程序卡死的问题。

答:为什么会出现这个问题呢?PySide6 应用程序是基于事件驱动的,主线程负责处理GUI事件。如果有耗时的操作任务,GUI 事件将被阻塞,应用程序会处于一个假死(crash)的状态。这个时候我们是无法同应用程序进行交互,只能等待任务完成并返回结果。

本篇文章来尝试来解决这个问题。


知识点📖📖

为了避免这种情况,我们应该将长时间运行的任务移动到单独的线程中执行。
这样,既可以在后台执行任务,又能保持应用程序的响应性(不会crash

而在PySide6中,可以使用这些线程类去(QThread、QObject、QRunnable 和 QtConcurrent)创建线程多线程,并采用 信号和槽机制 来将线程中的结果回传到主线程。

本文使用QThread来做介绍,因为使用方法都大差不差(值得注意的是,QThread 继承了 QObject


关于信号和槽机制,我会再起一篇文章来对它进行介绍。


实现

下面实现一个简单窗口,用于模拟耗时的操作任务从而导致线程阻塞。

主线程阻塞

代码

以下代码是一个简单的窗口,包含一个按钮和一个标签。
当点击按钮时候,会请求10次 https://www.csdn.net/,且每次睡眠1秒,并将请求结果和请求次数在标签上逐次打印出来。

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


import time

import requests

from PySide6.QtCore import QSize

from PySide6.QtWidgets import (QApplication, QPushButton, QLabel, QVBoxLayout, QWidget)


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):
        for idx in range(1, 11):
            time.sleep(1)
            res = requests.get('https://www.csdn.net/').text[:15]
            self.thread_finished((idx, res))

    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()

效果

窗口长这样:
在这里插入图片描述
运行效果这样:

理想的情况应该是打印10次请求次数和内容,但这里的标签只打印了第10次。很显然,这不是我们想要的。

因为有time.sleep耗时的任务,GUI 事件被阻塞,应用程序处于假死(crash)的状态。

在这里插入图片描述


使用QApplication.processEvents()

作用:用于处理当前事件队列中的所有事件,它的作用是让应用程序立即处理所有等待中的事件,并且能够让界面更加流畅,避免长时间的界面卡顿。

代码

setup_thread函数后面添加一行 QApplication.processEvents()

def setup_thread(self):
	for idx in range(1, 11):
	    time.sleep(1)
	    res = requests.get('https://www.csdn.net/').text[:15]
	    self.thread_finished((idx, res))
	    QApplication.processEvents()

效果

可以看到,现在实时打印出请求的次数和内容了!
它有用,但不是很有用!因为还有些卡顿。

请添加图片描述

使用 QThread多线程

代码

这一份代码与上面的代码作用一致,区别在于它用上了多线程。

这里的 MyThread继承了QThread类,在 PySide6 中,QThread 是一个线程类,可以用来创建新线程。
这里重写了run方法,当线程的 start() 方法被调用时,就会自动执行该方法。
点击按钮会触发 setup_thread 方法,在该方法中会创建线程类 MyThread 的实例并启动该线程。

# -*- 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://www.csdn.net/').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()

效果

效果很完美,应用程序没有丝毫卡顿,10次请求的也逐次在标签上打印出来。

请添加图片描述

总结

多线程效果很不错!

后话

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

是小菜欸

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

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

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

打赏作者

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

抵扣说明:

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

余额充值