当使用pyqt进行GUI工具编写时,往往会遇到处理网络传输数据或者文件读写等需要消耗一定时间的操作。

而在等待这些操作进行的时候GUI界面会因为进程阻塞而处于未响应状态,时间如果很长的话还会被系统判定为程序已停止响应并建议关闭,原因就是在进程阻塞时GUI界面是停止刷新的。

因此要处理进程阻塞,就需要引入多线程,分离数据处理和界面显示,这里就要说一下pyqt自带的QThread。

下面是一个进度条的例子。

from PyQt5 import QtWidgets, QtCore 
import sys 
from PyQt5.QtCore import * 
import time     

class Runthread(QtCore.QThread):     
     #  通过类成员对象定义信号对象
     _signal = pyqtSignal(str) 

    def __init__(self):
        super(Runthread, self).__init__()

    def __del__(self):
        self.wait()

    def run(self):
        for i in range(100):
            time.sleep(0.05)
            self._signal.emit(str(i))  # 注意这里与_signal = pyqtSignal(str)中的类型相同
        self._signal.emit(str(100))


class Example(QtWidgets.QWidget):   
 
    def __init__(self):
        super().__init__()
        # 按钮初始化
        self.button = QtWidgets.QPushButton('开始', self)
        self.button.setToolTip('这是一个 <b>QPushButton</b> widget')
        self.button.resize(self.button.sizeHint())
        self.button.move(120, 80)
        self.button.clicked.connect(self.start_login)  # 绑定多线程触发事件

        # 进度条设置
        self.pbar = QtWidgets.QProgressBar(self)
        self.pbar.setGeometry(50, 50, 210, 25)
        self.pbar.setValue(0)

        # 窗口初始化
        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('OmegaXYZ.com')
        self.show()

        self.thread = None  # 初始化线程

    def start_login(self):
        # 创建线程
        self.button.setEnabled(False)
        self.thread = Runthread()
        # 连接信号
        self.thread._signal.connect(self.call_backlog)  # 进程连接回传到GUI的事件
        # 开始线程
        self.thread.start()

    def call_backlog(self, msg):
        self.pbar.setValue(int(msg))  # 将线程的参数传入进度条
        if msg == '100':
            #self.thread.terminate()
            del self.thread
            self.button.setEnabled(True)   

 if name == "main":     
     app = QtWidgets.QApplication(sys.argv)     
     myshow = Example()     
     myshow.show()     
     sys.exit(app.exec_()) 

上面这个例子是通过继承QThread类,并重写该类的run方法,将业务逻辑计算放进run里运行。

需要注意的是: QThread只有run函数是在新线程里的,其他所有函数都在QThread生成的线程里。

但是因为QThread作为线程管理器,继承QThread类来实现业务逻辑的做法被QT作者多次批评,所以下面说一下多线程的正确姿势。

from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot 

import time 
import sys 

class Worker(QObject):
    finished = pyqtSignal()
    intReady = pyqtSignal(int)

    @pyqtSlot()
    def work(self): # A slot takes no params
        for i in range(1, 100):
            time.sleep(1)
            self.intReady.emit(i)
        self.finished.emit()


class Form(QWidget):
    def __init__(self):
       super().__init__()
       self.label = QLabel("0")

       # 1 - create Worker and Thread inside the Form
       self.worker = Worker()  # no parent!
       self.thread = QThread()  # no parent! 

       self.worker.intReady.connect(self.updateLabel) 
       self.worker.moveToThread(self.thread) 
       self.worker.finished.connect(self.thread.quit) 
       self.thread.started.connect(self.worker.work) 
       #self.thread.finished.connect(app.exit) 

       self.thread.start() 
       self.initUI() 

    def initUI(self): 
        grid = QGridLayout() 
        self.setLayout(grid) 
        grid.addWidget(self.label,0,0) 

        self.move(300, 150) 
        self.setWindowTitle('thread test') 

    def updateLabel(self, i): 
        self.label.setText("{}".format(i)) 
        #print(i) 


 app = QApplication(sys.argv)
 form = Form()
 form.show()
 sys.exit(app.exec_()) 

如上这个例子,将业务逻辑写到一个继承QObject的子类里,再建一个实例,调用继承自父类的moveToThread方法,这样就可以把该对象放在线程里面。

剩下的工作就是通过信号和槽的机制处理业务逻辑和返回结果了。一般来说,槽函数所在的类在哪个线程,这个函数就在哪个线程执行。

具体查看KionWong

其他例子可参考PyQt5多线程相关一些例子,下面说说其他相关。

1.PyQt5GUI编程时如果出现错误,界面停止响应但不会打印错误信息,调试bug非常不便。

如图,在pycharm中,菜单Run-Edit Configurations,然后勾选 Emulate terminal in output console 选项即可。

转载自feiqixia

 


生活只能倒着被理解,
必须正着被经历。
我们永远都不可能真正的及时理解生活,
因为在任何一个特定时刻,
我们根本无法找到必要的参考系来理解它。

——克尔凯廓尔