[ PyQt入門教程 ] PyQt5中多線程模塊QThread使用方法


  本文主要講解使用多線程模塊QThread解決PyQt界面程序唉執行耗時操作時,程序卡頓出現的無響應以及界面輸出無法實時顯示的問題。用戶使用工具過程中出現這些問題時會誤以為程序出錯,從而把程序關閉。這樣,導致工具的用戶使用體驗不好。下面我們通過模擬上述出現的問題並講述使用多線程QThread模塊解決此類問題的方法。

PyQt程序卡頓和無法實時顯示問題現象

   使用PyQt實現在文本框中每秒打印1個數字。程序代碼如下(QThread_Example_UI.py和QThread_Example_UI.ui見附錄):

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

import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow
from QThread_Example_UI import Ui_Form

class MyMainForm(QMainWindow, Ui_Form):
    def __init__(self, parent=None):
        super(MyMainForm, self).__init__(parent)
        self.setupUi(self)
        self.runButton.clicked.connect(self.display)

    def display(self):
        for i in range(20):
            time.sleep(1)
            self.listWidget.addItem(str(i))

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

 程序運行過程結果如下(點擊Run按鈕后界面出現未響應字樣同時程序也沒有出現每隔1秒打印1個數字,實際結果是循環結束后20個數字一同展示):

 問題分析

  上述實現的GUI程序都是單線程運行,對於需要執行一個特別耗時的操作時就會出現該問題現象。要解決這種問題可以考慮使用多線程模塊QThread。

多線程模塊QThread基本原理

  QThread是Qt的線程類中最核心的底層類。由於PyQt的的跨平台特性,QThread要隱藏所有與平台相關的代碼 要使用的QThread開始一個線程,可以創建它的一個子類,然后覆蓋其它QThread.run()函數。

class Thread(QThread):
    def __init__(self):
        super(Thread,self).__init__()
    def run(self):
        #

  接下來創建一個新的線程

thread = Thread()
thread.start()

  可以看出,PyQt的線程使用非常簡單,建立一個自定義的類(如Thread),自我繼承自QThread ,並實現其run()方法即可。在使用線程時可以直接得到Thread實例,調用其start()函數即可啟動線程,線程啟動之后,會自動調用其實現的run()的函數,該方法就是線程的執行函數 。

  業務的線程任務就寫在run()函數中,當run()退出之后線程就基本結束了,QThread有started和finished信號,可以為這兩個信號指定槽函數,在線程啟動和結束之時執行一段代碼進行資源的初始化和釋放操作,更靈活的使用方法是,在自定義的QThread實例中自定義信號,並將信號連接到指定的槽函數,當滿足一定的業務條件時發射此信號。

QThread類中的常用方法

  start():啟動線程

  wait():阻止線程,直到滿足如下條件之一

    (1)與此QThread對象關聯的線程已完成執行(即從run返回時),如果線程完成執行,此函數返回True,如果線程尚未啟動,也返回True

    (2)等待時間的單位是毫秒,如果時間是ULONG_MAX(默認值·),則等待,永遠不會超時(線程必須從run返回),如果等待超時,此函數將會返回False

  sleep():強制當前線程睡眠多少秒

QThread類中的常用信號

  started:在開始執行run函數之前,從相關線程發射此信號

  finished:當程序完成業務邏輯時,從相關線程發射此信號

使用QThread重新實現程序解決問題

   先繼承QThread類創建多線程,使用主線程更新界面,使用子線程實時處理數據(重寫run()函數,將耗時的操作放入run()函數中),最后將結果顯示到界面上。代碼如下:

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

import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow
from QThread_Example_UI import Ui_Form

class MyMainForm(QMainWindow, Ui_Form):
    def __init__(self, parent=None):
        super(MyMainForm, self).__init__(parent)
        self.setupUi(self)
        # 實例化線程對象
        self.work = WorkThread()
        self.runButton.clicked.connect(self.execute)

    def execute(self):
        # 啟動線程
        self.work.start()
        # 線程自定義信號連接的槽函數
        self.work.trigger.connect(self.display)

    def display(self,str):
        # 由於自定義信號時自動傳遞一個字符串參數,所以在這個槽函數中要接受一個參數
        self.listWidget.addItem(str)

class WorkThread(QThread):
    # 自定義信號對象。參數str就代表這個信號可以傳一個字符串
    trigger = pyqtSignal(str)

    def __int__(self):
        # 初始化函數
        super(WorkThread, self).__init__()

    def run(self):
        #重寫線程執行的run函數
        #觸發自定義信號
        for i in range(20):
            time.sleep(1)
            # 通過自定義信號把待顯示的字符串傳遞給槽函數
            self.trigger.emit(str(i))

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

 程序運行結果如下(實現了每隔1秒打印1個數字):

 小結

  如果你實現的工具需要執行特別耗時的操作,可以參考使用本文多線程QThread處理方法實現。當然,工具實際實現過程中的場景會比這復雜。比如,你的輸出並不是有固定時間間隔輸出的文本框,可以嘗試使用多次self.trigger.emit(str)方法進行操作。

附錄

  1、使用pyuic5轉換界面.ui程序后的QThread_Example_UI.py代碼如下:

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

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(498, 331)
        self.runButton = QtWidgets.QPushButton(Form)
        self.runButton.setGeometry(QtCore.QRect(190, 30, 75, 23))
        self.runButton.setObjectName("runButton")
        self.listWidget = QtWidgets.QListWidget(Form)
        self.listWidget.setGeometry(QtCore.QRect(30, 70, 431, 192))
        self.listWidget.setObjectName("listWidget")

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

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Qthread Example"))
        self.runButton.setText(_translate("Form", "Run"))

  2、Qtdesigner設計的界面源程序代碼QThread_Example_UI.ui如下:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>498</width>
    <height>331</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Qthread Example</string>
  </property>
  <widget class="QPushButton" name="runButton">
   <property name="geometry">
    <rect>
     <x>190</x>
     <y>30</y>
     <width>75</width>
     <height>23</height>
    </rect>
   </property>
   <property name="text">
    <string>Run</string>
   </property>
  </widget>
  <widget class="QListWidget" name="listWidget">
   <property name="geometry">
    <rect>
     <x>30</x>
     <y>70</y>
     <width>431</width>
     <height>192</height>
    </rect>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM