Python Pyqt5多线程更新UI代码实例(防止界面卡死)

下面是Python Pyqt5多线程更新UI代码实例(防止界面卡死)的完整攻略。

1. 背景

在使用PyQt5进行GUI开发时,假如某个计算机密集型的操作耗时较长,那么就可能会导致界面卡死,影响用户体验。为了避免这种情况,可以利用多线程更新UI的方式来解决。

2. 实现过程

2.1 创建线程

在PyQt5中利用QThread创建线程,在其run方法中执行需要进行的计算密集型操作,例如:

class Worker(QThread):

    finishSignal = pyqtSignal(int)

    def run(self):
        # 计算密集型操作
        # ...

    def __del__(self):
        self.wait()

2.2 进行信号槽连接

在主程序窗口中连接信号槽,将相关信号链接到主程序窗口的槽函数中。在槽函数中使用QMetaObject.invokeMethod() 方法来实现跨线程UI更新,例如:

class MainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.worker = Worker(self)
        self.worker.finishSignal.connect(self.update_ui)

    def on_start_button_clicked(self):
        self.worker.start()

    @pyqtSlot(int)
    def update_ui(self, progress):
        QMetaObject.invokeMethod(self.progress_bar, "setValue", Qt.QueuedConnection, Q_ARG(int, progress))

以上代码中,在worker的finishSignal信号被触发时,将会执行主程序窗口的update_ui槽函数,该函数中通过QMetaObject.invokeMethod()来更新UI。

2.3 代码完整示例

import sys
import time
from PyQt5.QtCore import pyqtSignal, QThread, QMetaObject, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow
from ui_mainwindow import Ui_MainWindow


class Worker(QThread):

    finishSignal = pyqtSignal(int)

    def run(self):
        for i in range(101):
            time.sleep(0.1)
            self.finishSignal.emit(i)


class MainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.worker = Worker(self)
        self.worker.finishSignal.connect(self.update_ui)

        self.start_button.clicked.connect(self.on_start_button_clicked)

    def on_start_button_clicked(self):
        self.worker.start()

    @pyqtSlot(int)
    def update_ui(self, progress):
        QMetaObject.invokeMethod(self.progress_bar, "setValue", Qt.QueuedConnection, Q_ARG(int, progress))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

以上代码中,我们通过Worker线程来模拟计算密集型操作,每隔0.1秒发射一次finishSignal信号,在主界面窗口的progress_bar进度条中进行更新。

3. 示例说明

3.1 示例1 - 多线程更新UI

我们在代码中添加一个QLabel控件,用于展示worker线程中的计数器变量。完整代码如下:

import sys
import time
from PyQt5.QtCore import pyqtSignal, QThread, QMetaObject, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow
from ui_mainwindow import Ui_MainWindow


class Worker(QThread):

    finishSignal = pyqtSignal(int)

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

    def run(self):
        for i in range(101):
            time.sleep(0.1)
            self.count = i
            self.finishSignal.emit(i)


class MainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.worker = Worker(self)
        self.worker.finishSignal.connect(self.update_ui)

        self.start_button.clicked.connect(self.on_start_button_clicked)

    def on_start_button_clicked(self):
        self.worker.start()

    @pyqtSlot(int)
    def update_ui(self, progress):
        QMetaObject.invokeMethod(self.progress_bar, "setValue", Qt.QueuedConnection, Q_ARG(int, progress))
        QMetaObject.invokeMethod(self.label, "setText", Qt.QueuedConnection, Q_ARG(str, f"Count: {self.worker.count}"))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

在点击start_button按钮事件后,我们会启动Worker线程执行计算密集型操作,此时在主窗口的之前创建好的progress_bar进度条上将会由10%到100%逐渐切换。而在主程序窗口的label标签中将会展示一个不断递增的,从0开始的计数器变量,该变量与worker线程的运行是在同步的。

3.2 示例2 - 多线程同时操作UI组件

在本示例中我们主要演示多个线程如何同时更新同一个UI组件。我们假设我们有两个人在分别进行下棋操作,他们会在不同的线程内进行操作和计算,而在UI显示这些下棋操作的时候,他们都需要更新同一个UI棋盘组件。我们将棋盘组件设计成一个QLabel,使用字母“A”来代表落子,而使用字母“B”来代表可以下子的空位。

import sys
import time
from PyQt5.QtCore import pyqtSignal, QThread, QMetaObject, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from ui_mainwindow import Ui_MainWindow


class PlayWorker(QThread):

    finishSignal = pyqtSignal(list)  # 信号带参数

    def __init__(self, parent=None):
        super().__init__(parent)
        self.player = None  # 已放置棋子
        self.step = None  # 待放置位置

    def run(self):
        if self.step and self.player:
            self.finishSignal.emit([self.player, self.step])


class MainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.last_player = ""  # 上一个下棋的人
        self.chessboard = [["B" for i in range(3)] for j in range(3)]  # 用列表记录棋盘状态
        self.chessboard_labels = [[QLabel() for i in range(3)] for j in range(3)]  # 用于显示棋盘的标签

        for row in range(3):
            for col in range(3):
                self.grid.addWidget(self.chessboard_labels[row][col], row, col)

        self.play_worker_player_1 = PlayWorker(self)
        self.play_worker_player_1.finishSignal.connect(self.update_chessboard)

        self.play_worker_player_2 = PlayWorker(self)
        self.play_worker_player_2.finishSignal.connect(self.update_chessboard)

        self.player_1_button.clicked.connect(self.on_player_1_button_clicked)
        self.player_2_button.clicked.connect(self.on_player_2_button_clicked)

    def on_player_1_button_clicked(self):
        self.last_player = "A"
        for row in range(3):
            for col in range(3):
                if self.chessboard[row][col] == "B":
                    self.chessboard[row][col] = self.last_player
                    self.play_worker_player_1.player = self.last_player
                    self.play_worker_player_1.step = (row, col)
                    self.play_worker_player_1.start()

    def on_player_2_button_clicked(self):
        self.last_player = "B"
        for row in range(3):
            for col in range(3):
                if self.chessboard[row][col] == "B":
                    self.chessboard[row][col] = self.last_player
                    self.play_worker_player_2.player = self.last_player
                    self.play_worker_player_2.step = (row, col)
                    self.play_worker_player_2.start()

    @pyqtSlot(list)
    def update_chessboard(self, move):
        player, step = move[0], move[1]
        row, col = step[0], step[1]
        self.chessboard_labels[row][col].setText(player)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

在上述代码中,我们创建了两个PlayWorker线程,它们分别代表不同的人。当任意一个人点击了自己的按钮,我们将遍历整个棋盘,找到下棋的空白位置。根据选手不同选举合适的worker线程来执行信号槽通信操作,在update_chessboard槽函数中进行棋盘UI组件的更新。

4. 总结

在本篇文章中我们讲解了如何利用PyQt5创建QThread线程并与主线程进行通信,实现多线程更新UI的效果。并展示了两个完整的程序案例来进一步演示该技术。虽然该技术可以解决界面卡死的问题,但使用多线程也会带来一定的额外开销,并且如果线程之间没有合理的同步机制,那么会存在操作顺序异常的风险,请开发人员谨慎使用。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Python Pyqt5多线程更新UI代码实例(防止界面卡死) - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • Python正则表达式总结分享

    Python正则表达式总结分享 正则表达式是一种用于描述字符串模式的语言,它可以用于匹配、查找、替换和割字符串。Python中的re模块供了对正则表达式的支持,可以方便地进行字符串的处理。本文将详细讲解Python中正则表达式的语法和re模块的常用函数以及两个示例说明。 正则表达式语法 正则表达式由一些特殊字符和普通字符组成,用于字符串模式。下面是一些常用的…

    python 2023年5月14日
    00
  • python def 定义函数,调用函数方式

    下面是完整的Python函数定义和调用攻略。 Python函数定义 在Python中,定义一个函数需要使用def关键字。函数定义的一般形式如下: def function_name(parameters): """函数文档字符串""" function_body return [expression…

    python 2023年6月5日
    00
  • OpenCV-Python实现图像梯度与Sobel滤波器

    下面我将为你详细讲解OpenCV-Python实现图像梯度与Sobel滤波器的完整攻略。 什么是图像梯度? 图像梯度是图像中灰度变化的快速变化率,也就是说,图像中某一个位置的梯度值越大,说明这个位置的像素值发生了快速的变化。 什么是Sobel滤波器? Sobel滤波器是一种常用的图像边缘检测算法。在OpenCV中,Sobel()函数可以用来创建Sobel滤波…

    python 2023年5月19日
    00
  • python正则表达式匹配[]中间为任意字符的实例

    Python正则表达式匹配[]中间为任意字符的实例 在Python中,我们可以使用正则表达式进行字符串匹配和替换。在正则表达式中,[]表示字符集,可以匹配其中任意一个字符。本攻略将详细讲解如何使用Python正则表达式匹配[]中间为任意字符的实例,包括如何使用.和[]进行匹配、如何使用re模块进行匹配。 使用.进行匹配 在Python中,我们可以使用.进行匹…

    python 2023年5月14日
    00
  • python写入xml文件的方法

    首先我们要了解一下Python中处理XML文件的库:ElementTree。它是Python标准库中的一个模块,支持XML文档的解析和生成。 准备工作 在使用ElementTree之前,我们需要先导入它: import xml.etree.ElementTree as ET 同时,我们也需要一个要写入的XML文件,比如这里假设它的路径为/path/to/xm…

    python 2023年6月3日
    00
  • 学习Python,你还不知道main函数吗

    学习 Python,你还不知道 main 函数吗? 在 Python 中,main 函数是一个特殊的函数,它通常用于测试和运行代码。main 函数是 Python 程序的入口点,即程序从哪里开始执行。 为什么要使用 main 函数? 使用 main 函数可以在测试时方便地运行您的代码,也可以增加代码的可读性。将代码封装在 main 函数中,能够使它更加易于理…

    python 2023年6月3日
    00
  • Python爬虫headers处理及网络超时问题解决方案

    Python爬虫headers处理及网络超时问题解决方案 简介 在使用Python进行爬虫开发时,会遇到对于爬虫脚本头部信息的设置和网络超时问题的解决。本文将详细讲述Python爬虫中headers的设置和超时问题的处理方法。 requests库中的headers设置 requests库是一个常用的Python爬虫库,其中的headers参数可以设置HTTP…

    python 2023年5月13日
    00
  • 用Python写脚本,实现完全备份和增量备份的示例

    让我们来详细讲解如何用Python写脚本实现完全备份和增量备份。 1. 准备工作 在编写Python备份脚本之前,我们需要安装必要的第三方库:pymysql和pymongo(如果你的脚本需要备份MySQL或MongoDB)。使用pip可以很方便地安装它们: pip install pymysql pymongo 2. 完全备份示例 以下是一个示例,它演示如何…

    python 2023年6月2日
    00
合作推广
合作推广
分享本页
返回顶部