PyQt子线程处理业务事件的问题解决

 更新时间:2024年02月07日 09:42:38   作者:阮靓仔  
在PyQt中,主线程通常是指GUI主循环所在的线程,而子线程则是执行实际工作的线程,本文主要介绍了PyQt子线程处理业务事件的问题解决,具有一定的参考价值,感兴趣的可以了解一下

在PyQt中是不推荐使用UI主线程来处理耗时操作的,会造成窗口组件阻塞。耗时操作一般放在子线程中。子线程处理完成后,可能需要更新窗口组件,但是PyQt不推荐使用子线程来更新主线程(也不是不能更新),这就用到了信号槽机制来更新主线程。

  • 在QObject的一个子类中创建一个信号(PyQt5.QtCore.pyqtSignal)属性
  • 将这个信号属性和其他类中的函数绑定,绑定的这个函数叫做整个信号的槽函数。一个信号可以和多个槽函数绑定。
  • 该信号发出时,就会调用对应的槽函数

可能会有疑问,槽函数被执行时所在的线程和发送信号的线程是不是同一个?

需要注意,信号一定义在QObject或其子类中。调用该属性的emit方法发出信号后,和该信号绑定的槽函数都将要被调用,但是调用的线程并不一定是发送信号的这个线程,这和PyQt中的线程亲和性(Thread Affinity)有关。

线程亲和性(Thread Affinity)

在 PyQt 中,一个对象可以被移动到不同的线程中,但一个对象在同一时刻只能属于一个线程。这是因为 Qt 使用线程亲和性(Thread Affinity)的概念来管理对象所属的线程。

每个 Qt 对象都与一个特定的线程相关联,即它的线程亲和性。对象的线程亲和性决定了该对象的槽函数是在哪个线程中执行。默认情况下,对象在创建时会与创建它的线程相关联,但可以使用 moveToThread 方法将对象移动到另一个线程中。

错误示例:

from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton,QFileDialog
from PyQt5.QtCore import QThread,pyqtSignal,QObject
import sys, threading

class MyWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        self.button = QPushButton('Hi')
        self.button.clicked.connect(self.on_click)
        self.setCentralWidget(self.button)

    def on_click(self):
        print("on_click",threading.current_thread().name)
        self.thread = MyThread(self)
        self.thread.start()

    def set_text(self,file_name):
        print("setText",threading.current_thread().name)
        self.button.setText(file_name)

class MyThread(QThread):

    def __init__(self,mv:QMainWindow) -> None:
        super().__init__(None)
        self.mv = mv
        
    def run(self):
        print('run',threading.current_thread().name)
        QThread.sleep(5)
        self.mv.set_text("Hello World")

if __name__ == '__main__':
    app = QApplication([])
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())

输出结果:

on_click MainThread
run Dummy-1
setText Dummy-1   //子线程更新UI,不推荐

使用信号槽机制

from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton,QFileDialog
from PyQt5.QtCore import QThread,pyqtSignal,QObject
import sys, threading
class MyWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        self.button = QPushButton('Hi')
        self.button.clicked.connect(self.on_click)
        self.setCentralWidget(self.button)

    def on_click(self):
        print("on_click",threading.current_thread().name)
        self.thread = MyThread(self)
        self.thread.pyqtSignal.connect(self.set_text)
        self.thread.start()

    def set_text(self,file_name):
        print("setText",threading.current_thread().name)
        self.button.setText(file_name)

class MyThread(QThread):
    pyqtSignal =  pyqtSignal(str)
    def __init__(self,mv:QMainWindow) -> None:
        super().__init__(None)
        self.mv = mv

    def run(self):
        print('run',threading.current_thread().name)
        QThread.sleep(5)
        self.pyqtSignal.emit("Hello World")

if __name__ == '__main__':
    app = QApplication([])
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())

输出结果:

on_click MainThread
run Dummy-1
setText MainThread //更新UI时,执行的线程为主线程

setText槽函数为什么会被主函数执行,就是因为线程亲和性,槽函数所在对象和MainThread绑定,当然会被主线程所执行。

但是这种将事务直接写在run,PyQt5是不推荐的,正确写法如下

创建一个类集成QObject,来做业务的处理。并将这个对象和新创建的线程通过moveToThread绑定,作为这个对象的亲和线程。将QThread的started信号和这个业务事件绑定。线程启动,发送started信号,业务对象开始处理业务,完成之后发送信号给主线程槽函数。

from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton,QFileDialog
from PyQt5.QtCore import QThread,pyqtSignal,QObject
import sys, threading
class MyWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        self.button = QPushButton('Hi')
        self.button.clicked.connect(self.on_click)
        self.setCentralWidget(self.button)

    def on_click(self):
        print("on_click",threading.current_thread().name)
        self.thread = QThread()

        self.myHander = MyHandler()
        self.myHander.moveToThread(self.thread)
        self.myHander.pyqtSignal.connect(self.set_text)

        self.thread.started.connect(self.myHander.handle)
        self.thread.start()

    def set_text(self,file_name):
        print("setText",threading.current_thread().name)
        self.button.setText(file_name)
class MyHandler(QObject):
    pyqtSignal =  pyqtSignal(str)
    def handle(self):
        print('handle',threading.current_thread().name)
        self.pyqtSignal.emit("Hello World")


if __name__ == '__main__':
    app = QApplication([])
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())

子线程中调用QFileDialog

如果在子线程中调用了QFileDialog窗口选择文件,QFileDialog窗口出现后几秒后程序会崩溃,代码如下

from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton,QFileDialog
from PyQt5.QtCore import QThread,pyqtSignal,QObject
import sys, threading
class MyWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        self.button = QPushButton('Hi')
        self.button.clicked.connect(self.on_click)
        self.setCentralWidget(self.button)

    def on_click(self):
        print("on_click",threading.current_thread().name)
        self.thread = MyThread(self)
        self.thread.pyqtSignal.connect(self.set_text)
        self.thread.start()

    def set_text(self,file_name):
        print("setText",threading.current_thread().name)
        self.button.setText(file_name)

class MyThread(QThread):
    pyqtSignal =  pyqtSignal(str)
    def __init__(self,mv:QMainWindow) -> None:
        super().__init__(None)
        self.mv = mv

    def run(self):
        print('run',threading.current_thread().name)
        file_name = QFileDialog.getOpenFileName(self.mv, '选择文件', './', 'Excel files(*.xlsx , *.xls)')
        print(file_name)
        self.pyqtSignal.emit("Hello World")

if __name__ == '__main__':
    app = QApplication([])
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())


输出结果:

on_click MainThread
run Dummy-1
QObject::setParent: Cannot set parent, new parent is in a different thread
CoCreateInstance failed (操作成功完成。)
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QApplication(0x21fb451d190), parent's thread is QThread(0x21fb443b430), current thread is MyThread(0x21fb8788df0)
CoCreateInstance failed (操作成功完成。)
QObject::startTimer: Timers cannot be started from another thread

问题原因

PyQt中,必须在主线程中来创建子对象。

到此这篇关于PyQt子线程处理业务事件的问题解决的文章就介绍到这了,更多相关PyQt子线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 基于Python编写一个ISBN查询工具

    基于Python编写一个ISBN查询工具

    这篇文章主要为大家详细介绍了如何利用Python编写一个简单的ISBN查询工具,可以用于图书管理、图书销售、图书收集和阅读等场景,需要的可以参考一下
    2023-05-05
  • Python开发围棋游戏的实例代码(实现全部功能)

    Python开发围棋游戏的实例代码(实现全部功能)

    围棋是一种古老而复杂的策略棋类游戏,起源于中国,已有超过2500年的历史,本文介绍了如何用Python开发一个简单的围棋游戏,实例代码涵盖了游戏的基本规则、界面设计、棋盘实现、棋子管理、游戏逻辑等多个方面,通过逐步实现落子、吃子、判断胜负等功能
    2024-12-12
  • Python 开发Activex组件方法

    Python 开发Activex组件方法

    Python强的功能就在于它无所不能。
    2009-11-11
  • python实现异步回调机制代码分享

    python实现异步回调机制代码分享

    本文介绍了python实现异步回调机制的功能,大家参考使用吧
    2014-01-01
  • python Aligo库设置json路径使用详解

    python Aligo库设置json路径使用详解

    这篇文章主要为大家介绍了python Aligo库设置json路径使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • 下载官网python并安装的步骤详解

    下载官网python并安装的步骤详解

    在本篇文章里小编给大家整理了关于下载官网python并安装的步骤详解,需要的朋友们参考学习下。
    2019-10-10
  • 探索 Python Restful 接口测试的奥秘

    探索 Python Restful 接口测试的奥秘

    掌握Python Restful 接口测试,让你的后端服务像流水一样顺畅,本指南将带你轻松穿梭于断言和请求之间,搞定所有测试难题,一起来看,让代码在你的指尖跳舞吧!
    2023-12-12
  • pycharm如何关闭pytest

    pycharm如何关闭pytest

    这篇文章主要介绍了pycharm如何关闭pytest问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • python实现一个通用的插件类

    python实现一个通用的插件类

    插件管理器用于注册、销毁、执行插件,本文主要介绍了python实现一个通用的插件类,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2024-04-04
  • Python3爬虫中Splash的知识总结

    Python3爬虫中Splash的知识总结

    在本篇文章里小编给大家整理的是关于Python3爬虫中Splash的知识总结内容,需要的朋友们可以学习参考下。
    2020-07-07

最新评论