前言
不管網上搜索QThread用法還是網上授課老師的教程,對QThread的介紹都是最簡單最基本的功能,如下:
創建線程類:
class Thread(QThread):
sinOut = pyqtSignal(str) # 定義信號便於傳遞數據
def __init __(self):
super(Thread,self).__ init __()
def run(self):
#線程相關的代碼
...
# 發射信號
self.sinOut.emit(file_str)
創建一個新的線程
thread = Thread()
thread.start()
舉例的代碼也是上面這幾行代碼的反覆使用,比如弄個定時器顯示個時間什麼的。但是,再我們平時開發中,並不會這麼簡單,那麼坑就來了。
第一個坑:給線程類傳參
在UI中調用線程一般是因爲一些比較耗時的工作會導致界面假死,但是往往這些耗時工作需要一些現有數據,那麼就需要給線程傳參,上面線程內定義的信號傳遞數據是把線程內的數據傳遞出去,但是我這裏是想說把數據傳遞進來,讓線程進行一些耗時運算,運算完了再把運算結果數據傳遞出去。這裏只要注意,傳參只能在線程類的 init(self,agrs)中傳參,run(self)是不能傳參的!傳參的類型很多,可以是數據也可以是對象。
第二個坑:傳遞一個類的實例
比如我定義了一個類進行數據運算,運算耗時很長,我需要把數據運算類的工作使用多線程處理, 數據運算類:
class ModifyData(object):
def __init__(self, data):
self.data = data # 接收原始數據
def analyze(self):
... # 數據處理過程省略
return self.data # 返回數據處理結果
那麼在UI窗口中就要使用多線程了,好吧,先定義一個線程類,上面第一個坑提到了傳參的問題,我們直接在線程類初始化時加入參數 argument_,這個參數是用來接受上面定義的數據處理類ModifyData():
class MoreThreadUse(QtCore.QThread):
update_date = QtCore.pyqtSignal(pandas.DataFrame) # 定義信號
def __init__(self, argument_):
super().__init__()
self.fuc = argument_
def run(self):
a = self.fuc.analyze()
self.update_date.emit(a)
time.sleep(10)
print("")
這裏還需要一個UI的工作界面類,我們是在這個UI中使用線程進行數據分析的,但是在這個Ui類中,我們如何把前面的數據分析類交給多線程來處理呢?直接把類名ModifyData傳遞過去嗎?對不起!當你用start()啓動線程的時候會直接崩潰!有兩種傳遞方式:第一、先把數據處理類ModifyData實例化,再把實例化對象傳參過去!第二, 把原始數據傳參給自定義多線程類,在多線程類的初始化中方法中實例化數據處理類ModifyData;下面是UI的工作界面類,使用第一種方法開啓多線程:
class ThreadUI(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("UI")
self.resize(500, 300)
self.input_line = QtWidgets.QLineEdit(self)
self.input_line.resize(400, 200)
def abc(self):
path = "E:\\*****\\***.xls"
data = pandas.read_excel(path)
ann = ModifyData(data)
self.thread_1 = MoreThreadUse(ann)
self.thread_1.update_date.connect(self.handle_display)
self.thread_1.start()
def handle_display(self, data):
self.input_line.setText(data)
第三個坑:信號傳參
大家都在說,在線程類種定義一個信號,方便與傳遞數據,雖然可以傳遞很多常見數據,但是並非所有數據都可以傳遞,上面的ui類種可以看到,要傳遞的數據是pandas打開的一個excel表格,其實這個表格有幾萬條數據,上面的往線程內傳參解決了,那使用線程數據處理完了,如何把數據傳遞出來呢?用信號嗎?不可以,直接崩潰了!因爲數據格式是pandas.DataFrame ,信號不能傳遞這類數據,肯定還有其他類型不能傳遞,這裏不列舉了,只要知道這回事就可以了,那麼怎麼處理呢?信號是肯定不能用了,我暫時沒想到更好的辦法,我這裏是使用了寫入文件處理的,因爲我的數據分析非常耗時,所有分析過程種可以做很多其他事情,直接把分析結果寫入一個文件就ok了。正確的代碼邏輯如下:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import pandas
import time
class MoreThreadUse(QtCore.QThread):
# 多線程,傳遞參數爲一個類的實例,不用信號傳遞數據,直接把數據寫入文件
def __init__(self, argument_):
super().__init__()
self.fuc = argument_
def run(self):
self.fuc = ModifyData(self.fuc)
data = self.fuc.analyze()
data.to_excel("D:\\****.xls") # 直接把數據處理結果寫入文件,使用pandas.to_xxx寫文件很方便
time.sleep(10)
print(a)
class ThreadUI(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("UI")
self.resize(500, 300)
self.input_line = QtWidgets.QLineEdit(self)
self.input_line.resize(400, 200)
def abc(self):
path = "E:\\*****\\***.xls"
data = pandas.read_excel(path)
ann = ModifyData(data)
self.thread_1 = MoreThreadUse(data)
self.thread_1.start()
def handle_display(self, data):
self.input_line.setText(data)
class ModifyData(object):
def __init__(self, data):
self.data = data
def analyze(self):
... # 數據處理過程省略
return self.data # 返回數據處理結果
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main = ThreadUI()
main.abc()
main.show()
sys.exit(app.exec_())
最後一個坑:創建線程類的變量
上面的例子種可以看到,線程類實例化時變量是 self.thread_1 一定要加上self,如果不加,thread_1就是一個局部變量,當其所在方法運行結束的時候,它的生命週期也都結束了,但是這個線程裏的程序很有可能還沒有運行完!可能會報錯:QThread :Destroyed while thread is still running!