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爬虫XPath语法

    一文掌握Python爬虫XPath语法

    这篇文章主要介绍了一文掌握Python爬虫XPath语法,xpath是一门在XML和HTML文档中查找信息的语言,可用来在XML和HTML文档中对元素和属性进行遍历,XPath 通过使用路径表达式来选取 XML 文档中的节点或者节点集。下面会更学习的介绍,需要的朋友可以参考一下
    2021-11-11
  • 用Python每天自动给女友免费发短信

    用Python每天自动给女友免费发短信

    大家好,本篇文章主要讲的是用Python每天自动给女友免费发短信,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2021-12-12
  • Python 数据科学 Matplotlib图库详解

    Python 数据科学 Matplotlib图库详解

    Matplotlib 是 Python 的二维绘图库,用于生成符合出版质量或跨平台交互环境的各类图形。今天通过本文给大家分享Python 数据科学 Matplotlib的相关知识,感兴趣的朋友一起看看吧
    2021-07-07
  • Python实现统计文章阅读量的方法详解

    Python实现统计文章阅读量的方法详解

    这篇文章主要为大家详细介绍了如何溧阳Python语言实现统计文章阅读量的功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-02-02
  • Pycharm创建文件时自动生成文件头注释(自定义设置作者日期)

    Pycharm创建文件时自动生成文件头注释(自定义设置作者日期)

    这篇文章主要介绍了Pycharm创建文件时自动生成文件头注释(自定义设置作者日期),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • Python中pygame的mouse鼠标事件用法实例

    Python中pygame的mouse鼠标事件用法实例

    这篇文章主要介绍了Python中pygame的mouse鼠标事件用法,以完整实例形式详细分析了pygame响应鼠标事件的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-11-11
  • Python XML RPC服务器端和客户端实例

    Python XML RPC服务器端和客户端实例

    这篇文章主要介绍了Python XML RPC服务器端和客户端实例,本文给出了实现代码以及运行效果,需要的朋友可以参考下
    2014-11-11
  • python opencv人脸检测提取及保存方法

    python opencv人脸检测提取及保存方法

    今天小编就为大家分享一篇python opencv人脸检测提取及保存方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • 学习Python需要哪些工具

    学习Python需要哪些工具

    这篇文章主要介绍了学习Python需要哪些工具,帮助大家开始学习python编程,感兴趣的朋友可以了解下
    2020-09-09
  • python中利用matplotlib读取灰度图的例子

    python中利用matplotlib读取灰度图的例子

    今天小编就为大家分享一篇python中利用matplotlib读取灰度图的例子,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12

最新评论