为了防止主界面卡顿,不得不学习子线程。遇到了一些问题并解决,做下总结,留作以后查阅。也希望能给同级别的初学者提供参考,少走点弯路。
先写代码,子线程的代码基本上很固定,我经过学习总结如下。
from PyQt5 import QtCore
import time
import traceback
class Thread(QtCore.QThread):
# 完成信号
signal_finished = QtCore.pyqtSignal()
# 重写构造函数,设置父类参数,实例化时将父类设置为主界面ui,
# 可保证线程的生命周期。
# 此处可用桥接的方式传入主ui,但可能造成参数多次多级别传递,
# 不方便维护。我推荐将主ui做成单例模式,方便其他模块调用。
def __init__(self, parent=None):
super().__init__(parent=parent)
# 非正常结束时触发,防止错误: Destroyed while thread is still running
# (下面统一称为[destroyed错误...])
def __del__(self):
self.wait()
def run(self):
# 主线程无法获取子线程的错误信息,用try和traceback返回错误信息。
try:
print('线程开始')
# 这里写代码,代码也可写成函数在此调用,暂时用time.sleep代替
time.sleep(5)
# 运行完代码,用quit函数退出线程,这句最好有,
# 配合__del__(self)防止[destroyed错误...]
self.quit()
# 下面这句不要尝试放在run里面,会弹出提示Thread tried to wait on itself,
# 虽不会出错,但应该没用。
# self.wait()
self.signal_finished.emit() # 经测试,信号可以放在quit后面,不影响信号发出
# 因为quit之后还需要1丢丢时间,立即结束可能出现[destroyed错误...],
# 因为有了__del__(self)所以不考虑这个延迟了,可以留着做双保险。
# time.sleep(1)
except:
print(traceback.format_exc())
# 运行线程, 将QThread的父类设为 main_window.ui ,保证生命周期.
# 建议将main_window做成单例,单独模块并直接实例化,
# 将uic.loadUi("main_window_ui.ui")封装在函数中,APP后调用(这里不细说了)
def run_thread():
# 实例化需要主界面配合,主界面不在这说了,说了就太多了。
th = Thread(main_window.ui)
th.signal_finished.connect(finished_thread)
th.start()
# th.wait() # 这句不能有,会卡主界面,我搞子线程就是为了不卡主界面
# 线程信号signal_finished的槽函数,可以用来放弹窗(弹窗等控件不能放在子线程中),
# 也可以激活别的线程。
def finished_thread():
pass
如果用的pycharm,推荐:运行>编辑配置>模拟终端>模拟输出控制台的终端:打钩。这样会弹出错误提示。
错误:Destroyed while thread is still running
这个错误最常见,意思是子线程“非正常终结”,会导致程序崩溃。
下面是我的理解,供参考:
原因1:生命周期不足
子线程的生命周期依赖实例化它的对象,或者它的父类。
注意“生命周期”这个词,很关键。生命周期就是指在内存中从开始到结束的时间,子线程的生命周期就是它运行所需要的时间;函数的生命周期就是函数从调用到其运行完毕;class就是从实例化到被回收,主界面ui就是从双击运行程序到程序结束。
举例: th = MyThread(),这个写法放在函数中就很容易报这个错误,因为函数的生命周期一般是比子线程短的多。
修改1:绑定类,写成 self.th = MyThread(),这样的话,只要所在的类没有被回收,就能保证子线程一直运行。
修改2(推荐):将主界面ui作为父类传入,写成 th = MyThread(main_window.ui),这样的话,只要程序不退出,就能保证子线程一直运行。
原因2:未安全退出(子线程完成run后不会立刻停止)
为了保证子线程安全退出,推荐quit函数配合__del__函数
1.在run函数末尾添加quit函数:self.quit()
2.子线程类添加下面的代码
def __del__(self):
self.wait()
错误:闪退
这个错误可能是上面的错误导致,也可能是在子线程中运行了控件,注意“控件”2个字,推荐子线程所有的数据交流全部用信号,也就是说子线程只操作信号,不要操作任何控件。
信号可以传输任意想要的数据,注意在创建信号时写明参数个数和数据类型,槽函数参数与其一一对应:
signal_msg1 = QtCore.pyqtSignal(str)
signal_msg2 = QtCore.pyqtSignal(str, bool)
signal_msg3 = QtCore.pyqtSignal(list, dict)
感谢:
我查阅了很多前辈的资料,因为太多,所以在此不罗列了,感谢前辈们的分享!!