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中的变量。让线程类彻底释放。

posted @ 2022-07-11 17:32  莫问哥哥  阅读(7523)  评论(0编辑  收藏  举报