Python PyQt5-QThread:在线程仍在运行时销毁
我试图弄明白,如果我试图在线程完成后再次运行线程,为什么这段代码会崩溃 第一次单击“启动5个线程”时,它运行良好并完成。但如果我再次点击它。整个程序崩溃,当线程仍在运行时,我得到QThread:destromed错误 此代码是在web上找到的。我正在努力从中学习Python PyQt5-QThread:在线程仍在运行时销毁,python,pyqt,pyqt5,qthread,Python,Pyqt,Pyqt5,Qthread,我试图弄明白,如果我试图在线程完成后再次运行线程,为什么这段代码会崩溃 第一次单击“启动5个线程”时,它运行良好并完成。但如果我再次点击它。整个程序崩溃,当线程仍在运行时,我得到QThread:destromed错误 此代码是在web上找到的。我正在努力从中学习 import time import sys from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import QA
import time
import sys
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QTextEdit, QVBoxLayout, QWidget
def trap_exc_during_debug(*args):
# when app raises uncaught exception, print info
print(args)
# install exception hook: without this, uncaught exception would cause application to exit
sys.excepthook = trap_exc_during_debug
class Worker(QObject):
"""
Must derive from QObject in order to emit signals, connect slots to other signals, and operate in a QThread.
"""
sig_step = pyqtSignal(int, str) # worker id, step description: emitted every step through work() loop
sig_done = pyqtSignal(int) # worker id: emitted at end of work()
sig_msg = pyqtSignal(str) # message to be shown to user
def __init__(self, id: int):
super().__init__()
self.__id = id
self.__abort = False
@pyqtSlot()
def work(self):
"""
Pretend this worker method does work that takes a long time. During this time, the thread's
event loop is blocked, except if the application's processEvents() is called: this gives every
thread (incl. main) a chance to process events, which in this sample means processing signals
received from GUI (such as abort).
"""
thread_name = QThread.currentThread().objectName()
thread_id = int(QThread.currentThreadId()) # cast to int() is necessary
self.sig_msg.emit('Running worker #{} from thread "{}" (#{})'.format(self.__id, thread_name, thread_id))
for step in range(100):
time.sleep(0.1)
self.sig_step.emit(self.__id, 'step ' + str(step))
# check if we need to abort the loop; need to process events to receive signals;
app.processEvents() # this could cause change to self.__abort
if self.__abort:
# note that "step" value will not necessarily be same for every thread
self.sig_msg.emit('Worker #{} aborting work at step {}'.format(self.__id, step))
break
self.sig_done.emit(self.__id)
def abort(self):
self.sig_msg.emit('Worker #{} notified to abort'.format(self.__id))
self.__abort = True
class MyWidget(QWidget):
NUM_THREADS = 5
# sig_start = pyqtSignal() # needed only due to PyCharm debugger bug (!)
sig_abort_workers = pyqtSignal()
def __init__(self):
super().__init__()
self.setWindowTitle("Thread Example")
form_layout = QVBoxLayout()
self.setLayout(form_layout)
self.resize(400, 800)
self.button_start_threads = QPushButton()
self.button_start_threads.clicked.connect(self.start_threads)
self.button_start_threads.setText("Start {} threads".format(self.NUM_THREADS))
form_layout.addWidget(self.button_start_threads)
self.button_stop_threads = QPushButton()
self.button_stop_threads.clicked.connect(self.abort_workers)
self.button_stop_threads.setText("Stop threads")
self.button_stop_threads.setDisabled(True)
form_layout.addWidget(self.button_stop_threads)
self.log = QTextEdit()
form_layout.addWidget(self.log)
self.progress = QTextEdit()
form_layout.addWidget(self.progress)
QThread.currentThread().setObjectName('main') # threads can be named, useful for log output
self.__workers_done = None
self.__threads = None
def start_threads(self):
self.log.append('starting {} threads'.format(self.NUM_THREADS))
self.button_start_threads.setDisabled(True)
self.button_stop_threads.setEnabled(True)
self.__workers_done = 0
self.__threads = []
for idx in range(self.NUM_THREADS):
worker = Worker(idx)
thread = QThread()
thread.setObjectName('thread_' + str(idx))
self.__threads.append((thread, worker)) # need to store worker too otherwise will be gc'd
worker.moveToThread(thread)
# get progress messages from worker:
worker.sig_step.connect(self.on_worker_step)
worker.sig_done.connect(self.on_worker_done)
worker.sig_msg.connect(self.log.append)
# control worker:
self.sig_abort_workers.connect(worker.abort)
# get read to start worker:
# self.sig_start.connect(worker.work) # needed due to PyCharm debugger bug (!); comment out next line
thread.started.connect(worker.work)
thread.start() # this will emit 'started' and start thread's event loop
# self.sig_start.emit() # needed due to PyCharm debugger bug (!)
@pyqtSlot(int, str)
def on_worker_step(self, worker_id: int, data: str):
self.log.append('Worker #{}: {}'.format(worker_id, data))
self.progress.append('{}: {}'.format(worker_id, data))
@pyqtSlot(int)
def on_worker_done(self, worker_id):
self.log.append('worker #{} done'.format(worker_id))
self.progress.append('-- Worker {} DONE'.format(worker_id))
self.__workers_done += 1
if self.__workers_done == self.NUM_THREADS:
self.log.append('No more workers active')
self.button_start_threads.setEnabled(True)
self.button_stop_threads.setDisabled(True)
# self.__threads = None
@pyqtSlot()
def abort_workers(self):
self.sig_abort_workers.emit()
self.log.append('Asking each worker to abort')
for thread, worker in self.__threads: # note nice unpacking by Python, avoids indexing
thread.quit() # this will quit **as soon as thread event loop unblocks**
thread.wait() # <- so you need to wait for it to *actually* quit
# even though threads have exited, there may still be messages on the main thread's
# queue (messages that threads emitted before the abort):
self.log.append('All threads exited')
if __name__ == "__main__":
app = QApplication([])
form = MyWidget()
form.show()
sys.exit(app.exec_())
导入时间
导入系统
从PyQt5.QtCore导入QObject、QThread、pyqtSignal、pyqtlot
从PyQt5.QtWidgets导入QApplication、QPushButton、QTextEdit、QVBoxLayout、QWidget
def trap_exc_调试期间(*args):
#当应用程序引发未捕获异常时,打印信息
打印(args)
#安装异常挂钩:如果没有这个,未捕获的异常将导致应用程序退出
sys.excepthook=trap\u exc\u调试期间
班级工作人员(QObject):
"""
必须从QObject派生才能发出信号,将插槽连接到其他信号,并在QThread中操作。
"""
sig_step=pyqtSignal(int,str)#工作者id,步骤描述:通过work()循环每一步发出
sig_done=pyqtSignal(int)#工作人员id:在工作结束时发出()
sig_msg=pyqtSignal(str)#向用户显示的消息
定义初始化(self,id:int):
super()。\uuuu init\uuuuu()
self.\uu id=id
self.\uu abort=False
@pyqtSlot()
def工作(自我):
"""
假设此辅助方法的工作时间很长。在此期间,线程
事件循环被阻止,除非调用了应用程序的processEvents():这将提供
线程(包括main)有机会处理事件,在本示例中,这意味着处理信号
从GUI接收(例如中止)。
"""
thread_name=QThread.currentThread().objectName()
thread_id=int(QThread.currentThreadId())#强制转换为int()是必需的
self.sig_msg.emit('Running worker#{}from thread“{}”(#{})').format(self.sig_id,thread_name,thread_id))
对于步进范围(100):
睡眠时间(0.1)
self.sig_step.emit(self.sig_id,'step'+str(step))
#检查是否需要中止循环;需要处理事件以接收信号;
app.processEvents()#这可能会导致对self的更改。u中止
如果自动终止:
#请注意,对于每个线程,“步长”值不一定相同
self.sig_msg.emit('Worker{}正在步骤{}中止工作。格式(self.sig_id,步骤))
打破
self.sig\u done.emit(self.\u id)
def中止(自我):
self.sig_msg.emit('Worker{}通知中止'。格式(self.\uu id))
self.\uuu abort=True
类MyWidget(QWidget):
线程数=5
#sig_start=pyqtSignal()#仅由于PyCharm调试器错误(!)而需要
sig_abort_workers=pyqtSignal()
定义初始化(自):
super()。\uuuu init\uuuuu()
self.setWindowTitle(“线程示例”)
form_layout=QVBoxLayout()
self.setLayout(表单_布局)
自我调整大小(400800)
self.button\u start\u threads=QPushButton()
self.button\u start\u threads.clicked.connect(self.start\u threads)
self.button_start_threads.setText(“start{}threads.format(self.NUM_threads))
form\u layout.addWidget(self.button\u start\u线程)
self.button\u stop\u threads=QPushButton()
self.button\u stop\u threads.clicked.connect(self.abort\u workers)
self.button\u stop\u threads.setText(“停止线程”)
self.button\u stop\u threads.setDisabled(True)
form\u layout.addWidget(self.button\u stop\u线程)
self.log=QTextEdit()
form_layout.addWidget(self.log)
self.progress=QTextEdit()
form_layout.addWidget(self.progress)
QThread.currentThread().setObjectName('main')#可以命名线程,这对日志输出很有用
self.\u workers\u done=无
self.\uu线程=无
def start_线程(自):
self.log.append('starting{}threads'。格式(self.NUM_threads))
self.button\u start\u threads.setDisabled(True)
self.button\u stop\u threads.setEnabled(True)
self.\uu workers\u done=0
self._线程=[]
对于范围内的idx(self.NUM_线程):
工人=工人(idx)
thread=QThread()
setObjectName('thread_'+str(idx))
self.u threads.append((线程,工作线程))#也需要存储工作线程,否则将被gc
worker.moveToThread(线程)
#从worker获取进度消息:
worker.sig\u step.connect(self.on\u worker\u step)
worker.sig\u done.connect(self.on\u worker\u done)
worker.sig_msg.connect(self.log.append)
#控制工人:
self.sig\u abort\u workers.connect(worker.abort)
#获取读取以启动工作进程:
#self.sig_start.connect(worker.work)#由于PyCharm调试器错误(!)而需要;注释掉下一行
线程.started.connect(worker.work)
thread.start()#这将发出'started'和start线程的事件循环
#self.sig_start.emit()#由于PyCharm调试器错误(!)而需要
@pyqtSlot(int,str)
工作步骤上的定义(self,worker\u id:int,data:str):
self.log.append('Worker{}:{}'。格式(Worker\u id,数据))
self.progress.append(“{}:{}.”格式(工作者id,数据))
@pyqtSlot(int)
已完成工作人员的定义(自身、工作人员id):
self.log.append('worker#{}done'。格式(worker#u id))
self.progress.append('--Worker{}DONE'。格式(Worker_id))
自身工作人员工作完成+=1
如果self.\u workers\u done==self.NUM\u线程:
self.log.append('不再有工作进程处于活动状态')
self.button\u start\u threads.setEnabled(True)
self.button\u stop\u threads.setDisabled(True)
#self.\uu线程=无
@pyqtSlot()
def中止工作人员(自身):
self.sig\u abort\u workers.emit()
self.log.append('要求每个工作进程中止')
thread = QThread()
thread = QThread(parent=self)