QThread: Destroyed while thread is still running
适合谁读
会基本的线程操作。
初衷
操作线程的时候,遇到了这样的错误:
QThread: Destroyed while thread is still running
查阅了很多资料,做法很多,都说可以解决问题。但并不适用于所有的场景。因为使用线程的方式很多。应用场景很多,如果能知其所以然,解决这样的问题就很容易,否则,暂时性的解决了问题,最后还是会再次遇到。
原因
其根本原因就是:线程还在运行,并没有真正的释放,但线程引用的变量生命周期已经结束了。
以下就我遇到的几种情况,做说明:
情况一:开启线程后直接报错
def some_function(self):
thread_recv = CustomQThread() # CusetomQThread 是自定义的线程类。
thread_recv.start()
错误解析:
因为 thread_recv 的生命周期在方法执行结束后就结束了,也就是说,thread_recv.start()之后,就结束了,但线程才刚刚开始,所以会报错。
修改方式:
将 thread_recv 这个方法内的局部变量改为全局变量,self.thread_recv 即可。
情况二:关闭线程释放资源后报错
线程的关闭有很多种:可以通过标识符来退出,如下:
from PyQt5.QtCore import QThread,pyqtSignal
class Woker(QThread):
printf = pyqtSignal(str)
def __init__(self,id):
super(Woker,self).__init__()
self.thread_id = id
self.run_thread = True
def run(self):
while self.run_thread:
s = "thread is running. {0}".format(self.thread_id )
self.printf.emit(s)
self.msleep(300)
def cancel(self):
self.run_thread = False
这样的情况,我们可以直接调用 cancel方法来退出,还可以这样:
from PyQt5.QtCore import QThread,pyqtSignal
class Woker(QThread):
printf = pyqtSignal(str)
def __init__(self,id):
super(Woker,self).__init__()
self.thread_id = id
def run(self):
while self.isInterruptionRequested() == False:
s = "thread is running. {0}".format(self.thread_id )
self.printf.emit(s)
self.msleep(300)
def cancel(self):
self.requestInterruption()
这样的情况都是可以的,有人说可以使用 quit(), exit() 等这样的方法。实则,如果不是在run种 使用了 exec()这样的消息循环方法,是不需要调用 quit()或是 exit() 的。
基于上述两种方式的线程类,我们一般会是这样控制:
● 创建线程
创建200个线程,模式实际可能出现的场景。
def btnCreateMutiClicked(self):
for i in range(200):
work = Woker(str(i))
work.printf.connect(lambda s:self.ui.textEdit.append(s))
work.start()
self.work_list.append(work)
这里可以看到,为了避免线程管理类work 生命周期 结束,我们将它放到了 work_list中。
● 释放线程
# 关闭线程
def btnDestoryAllClicked(self):
for w in self.work_list:
if isinstance(w,Woker):
w.cancel()
self.work_list.remove(w)
使用 btnDestoryAllClicked 这样的方法释放所有线程。看似没什么问题,最后程序还是会闪退,闪退的原因是,在线程执行 cancel方法后,线程没有彻底释放,还是在运行,但线程管理类被释放了,也就是上述代码中的 w被释放了,不过偶尔也不会闪退。但我们肯定不希望我们自己些的程序这么不稳定。
解决
在不懈的努力下,找到了解决办法:结合wait() 、 isFinished() 和 finished 信号槽。把创建函数改成这样:
def btnCreateMutiClicked(self):
for i in range(200):
work = Woker(str(i))
work.printf.connect(lambda s:self.ui.textEdit.append(s))
work.finished.connect(self.release)
work.start()
self.work_list.append(work)
在线程start之前,在finished信号槽上绑定了一个槽函数。 work.finished.connect(self.release)
把释放改成这样:
def btnDestoryAllClicked(self):
for w in self.work_list:
if isinstance(w,Woker):
w.cancel()
多了一个release方法,如下:
def release(self):
try:
for i,t in enumerate(self.work_list) :
if isinstance(t,Woker):
t.wait()
if t.isFinished():
del self.work_list[i]
except ValueError as ve:
pass
这个函数做了3件事:
1、使用 wait() 方法等待函数结束。
2、使用isFinished()方法判断线程是否结束。
3、删除线程管理类的变量,也就是存放在 work_list中的变量。让线程类彻底释放。