概述
项目场景:
使用pyqt5开发一个带有UI界面的图像数据处理程序
问题描述:
根据业务和UI界面分离的原则,为了使大量数据处理过程不影响UI主进程,于是使用了多线程的方式,利用pyqt5中的QThread类来创建数据处理的子线程,然后将数据传回UI主线程进行展示。然而在子线程处理数据过程中,由于文件读取等操作可能会产生异常,进而引起主UI线程的崩溃。为了提高程序的鲁棒性和健壮性,使用try...except
处理异常,然而子线程中产生的异常无法在主线程中捕获到。
# 数据处理的线程
class WorkThread(QThread):
finish = pyqtSignal(np.ndarray) # 定义一个信号,类型是numpy数组类型的图片
def __init__(self,path):
super(WorkThread, self).__init__()
self.path = path
def __del__(self):
self.wait()
def run(self):
image = cv2.imread(self.path)
time.sleep(5) # 模拟处理数据过程
self.finish.emit(image) # 处理好的图片数据发送出去
# 主UI线程
class MainWidget(QWidget):
def __init__(self):
super(MainWidget, self).__init__()
self.label = QLabel(self)
self.bt = QPushButton("处理",self)
self.bt.clicked.connect(self.pocessImage)
vBox = QVBoxLayout()
vBox.addWidget(self.bt)
vBox.addWidget(self.label)
self.setLayout(vBox)
def pocessImage(self):
self.bt.setEnabled(False)
path = "test.jpg"
workThread = WorkThread(path)
workThread.start() #启动线程
workThread.finish.connect(self.showImage)
def showImage(self,image):
self.bt.setEnabled(True)
# QImage通过numpy类型转化QImage类型时,注意后面两个参数写全
# openCV读取的通道顺序为BGR
self.label.setPixmap(QPixmap(QImage(image,image.shape[1], image.shape[0],image.shape[1]*3, QImage.Format_BGR888)))
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWidget()
window.show()
sys.exit(app.exec())
原因分析:
使用start()
方法启动子线程时,解释器会为子线程开辟独立的栈空间,于是主线程就无法获取子线程栈的信息。当子线程异常中止时,会在子线程中捕获处理而不会将此异常抛出给主线程。
解决方案:
在子线程中定义一个异常标志,如果线程异常退出,将该标志位设置为1,正常退出为0
class WorkThread(QThread):
finish = pyqtSignal(np.ndarray) # 定义一个信号,类型是numpy数组类型的图片
def __init__(self,path):
super(WorkThread, self).__init__()
self.path = path
self.exitcode = 0
# 如果线程异常退出,将该标志位设置为1,正常退出为0
self.exception = None
def run(self):
try:
image = cv2.imread(self.path)
time.sleep(2) # 模拟处理数据过程
self.finish.emit(image) # 处理好的图片数据发送出去
except Exception as e:
self.exitcode = 1
self.exception = e
class MainWidget(QWidget):
def __init__(self):
super(MainWidget, self).__init__()
self.label = QLabel(self)
self.bt = QPushButton("处理",self)
self.bt.clicked.connect(self.pocessImage)
vBox = QVBoxLayout()
vBox.addWidget(self.bt)
vBox.addWidget(self.label)
self.setLayout(vBox)
def pocessImage(self):
try:
self.bt.setEnabled(False)
path = "test.jp" # 错误路径,人为产生一个异常
self.workThread = WorkThread(path)
self.workThread.start() #启动线程
self.workThread.finish.connect(self.showImage)
if self.workThread.wait() and self.workThread.exitcode == 1: #self.workThread.wait()不可缺少,需要等主线程完成后再进行子线程活动
raise self.workThread.exception
except Exception as e:
self.bt.setEnabled(True)
QMessageBox.information(self, "提示:", '操作失败!')
def showImage(self,image):
self.bt.setEnabled(True)
# QImage通过numpy类型转化QImage类型时,注意后面两个参数写全
# openCV读取的通道顺序为BGR
self.label.setPixmap(QPixmap(QImage(image,image.shape[1], image.shape[0],image.shape[1]*3, QImage.Format_BGR888)))
问题基本上解决! 但是关于Pyqt多线程相关的知识还是没有完全掌握和理解,后面继续学习!
更新
在项目中有一个小功能,是用进度条来显示后台任务完成的进度。遇到的问题是,如果后台任务没有发生异常,进度条可以很好的显示进度,但是发生异常后,进度条窗口会进入“假死”状态,使用上面的方法无法解决这种问题,该进度条只能手动关闭才能显示后面的异常信息,否则进度条会一直显示。
新的解决思路
在子线程中创建一个异常信号,然后在子线程中捕获异常,如果子线程任务出现了异常就emit这个异常信号,在主UI线程中connect这个异常信号,如果接收成功了就说明子线程有异常,就可以对其进行相应的操作(直接关闭进度条)。
部分代码:
# 进度条类
class ProcessDialog(QDialog):
def __init__(self):
super(ProcessDialog,self).__init__()
self.resize(300,100)
vBox = QVBoxLayout()
label = QLabel("处理中...",self)
self.pbar = QProgressBar()
self.pbar.setMaximum(100)
self.timer = QBasicTimer()
self.step = 0
vBox.addWidget(label)
vBox.addWidget(self.pbar)
self.setLayout(vBox)
self.setWindowTitle('提示')
self.timer.start(10, self)
# 新建的窗口始终位于当前屏幕的最前面
self.setWindowFlags(Qt.WindowStaysOnTopHint)
# 阻塞父类窗口不能点击
self.setWindowModality(Qt.ApplicationModal)
self.setWindowFlags(Qt.WindowCloseButtonHint)
def timerEvent(self, e): # 监测,如果step大于等于100则关闭进度条
if self.step >= 100:
self.quit()
return
# if self.step == 0:
#
QTimer.singleShot(10000, self.quit)
def quit(self):
QApplication.processEvents() #会使窗口变换流畅
self.step = 0
self.pbar.setValue(self.step)
self.timer.stop()
self.close()
# 子线程类
class Mythread(Qthread):
finish = pyqtSignal(np.ndarray,int)
#处理完后待发送的信息信号,int用来控制进度条的进度
excepted = pyqtSignal(str) #发送一个异常信号
def __init__(self):
super(Mythread, self).__init__()
def run(self):
try:
# 进行耗时操作
for i in range(100):
self.finish.emit(image,i+1) # 处理完成后发送
except Exception as e:
self.excepted.emit(str(e)) # 将异常发送给UI进程
# 窗口UI类
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
# 部分代码省略
self.myThread = MyThread()
self.myThread.finish.connect(self.fun)
self.myThread.excepted.connect(self.threadException)
self.processDiaog = ProcessDialog()
if not self.processDiaog.exec_(): #显示进度条,如果手动关闭则终止子线程
self.myThread.terminate()
def fun(image,step): #正常处理函数
self.processDiaog.step = step
self.processDiaog.pbar.setValue(step)
# 对image数据进行展示
# 略
def threadException(self,message):
self.processDiaog.quit()
print(message) #打印子线程错误日志
最后
以上就是俏皮咖啡豆为你收集整理的pyqt5 Qthread的子线程异常处理项目场景:问题描述:原因分析:解决方案:更新的全部内容,希望文章能够帮你解决pyqt5 Qthread的子线程异常处理项目场景:问题描述:原因分析:解决方案:更新所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复