1. 程式人生 > 程式設計 >python GUI庫圖形介面開發之PyQt5 UI主執行緒與耗時執行緒分離詳細方法例項

python GUI庫圖形介面開發之PyQt5 UI主執行緒與耗時執行緒分離詳細方法例項

在做介面開發時,無論是移動端的Android,還是我們這裡講的PyQt5,經常會有一個介面開發準則,那就是UI主執行緒與耗時子執行緒一定要分開,主執行緒負責重新整理介面,耗時操作,如網路互動、磁碟IO等,都應該放在子執行緒裡執行,它們各司其職,保證系統正常執行,提升整體使用者體驗。

軟硬體環境

windows 10 64bit

PyQt5

Anaconda3 with python 3.6.5

例項程式碼

首先看下工程目錄結構

python GUI庫圖形介面開發之PyQt5 UI主執行緒與耗時執行緒分離詳細方法例項

main.py,這是工程入口檔案,它負責建立app

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

import sys

from PyQt5.QtWidgets import QApplication

from gui.mainwindow import MainWindow

if __name__ == '__main__':

  app = QApplication(sys.argv)
  main_window = MainWindow()
  main_window.show()
  sys.exit(app.exec_())

ui_mainwindow.py,負責介面的繪製,這個檔案通過designer圖形化工具作圖然後使用pyuic工具生成對應的python程式碼

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

# Form implementation generated from reading ui file '.\mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore,QtGui,QtWidgets

class Ui_MainWindow(object):
  def setupUi(self,MainWindow):
    MainWindow.setObjectName("MainWindow")
    MainWindow.resize(800,600)
    MainWindow.setMinimumSize(QtCore.QSize(800,600))
    MainWindow.setMaximumSize(QtCore.QSize(800,600))
    self.centralwidget = QtWidgets.QWidget(MainWindow)
    self.centralwidget.setObjectName("centralwidget")
    self.button_ok = QtWidgets.QPushButton(self.centralwidget)
    self.button_ok.setGeometry(QtCore.QRect(260,220,230,140))
    self.button_ok.setMinimumSize(QtCore.QSize(230,140))
    self.button_ok.setMaximumSize(QtCore.QSize(230,140))
    font = QtGui.QFont()
    font.setPointSize(50)
    self.button_ok.setFont(font)
    self.button_ok.setFocusPolicy(QtCore.Qt.TabFocus)
    self.button_ok.setObjectName("button_ok")
    MainWindow.setCentralWidget(self.centralwidget)
    self.statusbar = QtWidgets.QStatusBar(MainWindow)
    self.statusbar.setObjectName("statusbar")
    MainWindow.setStatusBar(self.statusbar)
    self.menubar = QtWidgets.QMenuBar(MainWindow)
    self.menubar.setGeometry(QtCore.QRect(0,800,23))
    self.menubar.setObjectName("menubar")
    self.menuFile = QtWidgets.QMenu(self.menubar)
    self.menuFile.setObjectName("menuFile")
    self.menuHelp = QtWidgets.QMenu(self.menubar)
    self.menuHelp.setObjectName("menuHelp")
    MainWindow.setMenuBar(self.menubar)
    self.actionExit = QtWidgets.QAction(MainWindow)
    self.actionExit.setObjectName("actionExit")
    self.actionCopy = QtWidgets.QAction(MainWindow)
    self.actionCopy.setObjectName("actionCopy")
    self.actionPaste = QtWidgets.QAction(MainWindow)
    self.actionPaste.setObjectName("actionPaste")
    self.actionCut = QtWidgets.QAction(MainWindow)
    self.actionCut.setObjectName("actionCut")
    self.actionHelp = QtWidgets.QAction(MainWindow)
    self.actionHelp.setObjectName("actionHelp")
    self.actionAbout = QtWidgets.QAction(MainWindow)
    self.actionAbout.setObjectName("actionAbout")
    self.action_query = QtWidgets.QAction(MainWindow)
    self.action_query.setObjectName("action_query")
    self.action_backupDB = QtWidgets.QAction(MainWindow)
    self.action_backupDB.setObjectName("action_backupDB")
    self.action_reset_mac = QtWidgets.QAction(MainWindow)
    self.action_reset_mac.setObjectName("action_reset_mac")
    self.menuFile.addSeparator()
    self.menuFile.addAction(self.actionExit)
    self.menuFile.addSeparator()
    self.menuHelp.addSeparator()
    self.menuHelp.addSeparator()
    self.menuHelp.addAction(self.actionAbout)
    self.menuHelp.addSeparator()
    self.menubar.addAction(self.menuFile.menuAction())
    self.menubar.addAction(self.menuHelp.menuAction())

    self.retranslateUi(MainWindow)
    QtCore.QMetaObject.connectSlotsByName(MainWindow)

  def retranslateUi(self,MainWindow):
    _translate = QtCore.QCoreApplication.translate
    MainWindow.setWindowTitle(_translate("MainWindow","分離UI主執行緒和工作執行緒"))
    self.button_ok.setText(_translate("MainWindow","確定"))
    self.menuFile.setTitle(_translate("MainWindow","File"))
    self.menuHelp.setTitle(_translate("MainWindow","Help"))
    self.actionExit.setText(_translate("MainWindow","退出"))
    self.actionHelp.setText(_translate("MainWindow","軟體使用說明"))
    self.actionAbout.setText(_translate("MainWindow","關於"))

mainwindow.py,主要負責介面上控制元件的事件處理

import time

from PyQt5.QtWidgets import QMainWindow

from gui.ui_mainwindow import *


class MainWindow(QMainWindow,Ui_MainWindow):

  def __init__(self,parent=None):
    super(MainWindow,self).__init__(parent)
    self.setupUi(self)

    # 繫結點選事件
    self.button_ok.clicked.connect(self.button_start)

  def button_start(self):

    self.button_ok.setChecked(True)
    self.button_ok.setDisabled(True)

    time.sleep(20)

這裡我們使用time.sleep(20)來模擬耗時任務,執行python main.py後一會,介面就會出現無響應,假死的現象,等到20秒過後,介面又恢復了正常,使用者體驗非常差。

python GUI庫圖形介面開發之PyQt5 UI主執行緒與耗時執行緒分離詳細方法例項

其實要解決這個問題,也非常簡單。我們將UI主執行緒中的time.sleep(20)移動到子執行緒中就可以了。PyQt5中提供了執行緒類QThread,我們繼承它並重寫它的run方法,新建一個新的檔案threads.py

# -*- coding: utf-8 -*-
import time

from PyQt5.QtCore import QThread,pyqtSignal

class WorkThread(QThread):

  # 使用訊號和UI主執行緒通訊,引數是傳送訊號時附帶引數的資料型別,可以是str、int、list等
  finishSignal = pyqtSignal(str)

  # 帶引數示例
  def __init__(self,ip,port,parent=None):
    super(WorkThread,self).__init__(parent)

    self.ip = ip
    self.port = port

  def run(self):
    '''
    重寫
    '''

    print('=============sleep======ip: {},port: {}'.format(self.ip,self.port))
    time.sleep(20)

    self.finishSignal.emit('This is a test.')
    return

注意到這裡我們使用了pyqtSignal,我們使用它來跟UI主執行緒通訊,一般用於介面元素的重新整理,在子執行緒的最後,我們傳送這個訊號。

對應的mainwindow.py,需要進行如下修改

from gui.threads import WorkThread

# 其它部分省略
def button_start(self):

  print('button_start clicked.')

  # 設定按鈕不可用
  self.button_ok.setChecked(True)
  self.button_ok.setDisabled(True)

  self.th = WorkThread(ip='192.168.1.1',port=4000)

  # 將執行緒th的訊號finishSignal和UI主執行緒中的槽函式button_finish進行連線
  self.th.finishSignal.connect(self.button_finish)

  # 啟動執行緒
  self.th.start()

def button_finish(self,msg):

  print('msg: {}'.format(msg))

  # 設定按鈕可用
  self.button_ok.setChecked(False)
  self.button_ok.setDisabled(False)

一頓操作之後,再次執行python main.py,介面就再也不會出現No Resonding的提示了,可以在子執行緒執行過程中可以隨意操作介面上的其它控制元件

更多相關知道請檢視下面的相關連結