Worker1线程中调用serialSend函数未执行问题求助
问题解决:PyQt5线程中调用主窗口串口发送方法无响应
核心原因
- 线程安全违规:PyQt明确规定非UI线程不能直接调用UI线程创建的对象方法,
serial对象在CustomMainWindow(UI线程)中初始化,Worker1线程直接调用serialSend会导致方法无法正常执行,甚至触发线程冲突。 - 全局变量引用无效:线程启动时
mainWindow可能未完成初始化,直接引用全局变量会导致无效调用。
标准解决方案:信号槽通信
利用PyQt的信号槽机制,让线程发送数据信号,主窗口在UI线程中接收并执行串口发送操作,这是线程与主界面通信的规范方式,完全符合线程安全要求。
具体修改步骤
- 为Worker1新增串口发送请求信号:
class Worker1(QThread): ImageUpdate = pyqtSignal(QImage) # 定义信号,用于传递需要发送的串口数据列表 SerialSendRequest = pyqtSignal(list)
- 在主窗口中连接信号与串口发送方法:
class CustomMainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) self.Worker = Worker1() # 连接Worker的串口发送请求信号到主窗口的serialSend方法 self.Worker.SerialSendRequest.connect(self.serialSend) self.Worker.start() self.Worker.ImageUpdate.connect(self.ImageUpdateSlot)
- 替换线程中的直接调用为发送信号:
# Worker1的run方法中,替换原有的mainWindow.serialSend调用 if iSee == True: self.SerialSendRequest.emit([2, controlXY])
修改后的完整代码
from GoodNessMain import * from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo import sys import cv2 import math class CustomMainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) self.Worker = Worker1() # 新增信号连接 self.Worker.SerialSendRequest.connect(self.serialSend) self.Worker.start() self.Worker.ImageUpdate.connect(self.ImageUpdateSlot) # ------------------------- SerialPort ------------------------- # self.serial = QSerialPort() self.serial.setBaudRate(115200) self.serial.readyRead.connect(self.onRead) portList = [] ports = QSerialPortInfo().availablePorts() for port in ports: portList.append(port.portName()) self.comL.addItems(portList) for i, port in enumerate(portList): comL = self.menuPortList.addAction(str(port)) comL.triggered.connect(self.onOpen) if i >= 2: break self.actionOpenPort.triggered.connect(self.onOpen) self.actionClosePort.triggered.connect(self.onClose) self.actionReset.triggered.connect(self.onReset) # ------------------------- SerialPort ------------------------- # self.speedSlider.valueChanged.connect(self.dSpeed) self.servoSlider.valueChanged.connect(self.dServo) self.ledC.stateChanged.connect(self.ledControl) self.QR_Forward.clicked.connect(self.dFORWARD) self.QR_Stop.clicked.connect(self.dSTOP) self.QR_Back.clicked.connect(self.dBACKWARD) def onReset(self): portlist = [] ports = QSerialPortInfo().availablePorts() for port in ports: portlist.append(port.portName()) self.comL.clear() self.comL.addItems(portlist) self.menuPortList.clear() for i, port in enumerate(portlist): comL = self.menuPortList.addAction(str(port)) comL.triggered.connect(self.onOpen) if i >= 2: break def onOpen(self): self.serial.setPortName(self.comL.currentText()) self.serial.open(QIODevice.ReadWrite) def onClose(self): self.serial.close() def onRead(self): if not self.serial.canReadLine(): return rx = self.serial.readLine() rxs = str(rx, 'utf-8').strip() data = rxs.split(',') if data[0] == '0': self.temperaturL01.display(data[1]) self.temperaturL02.display(data[2]) def serialSend(self, data): txs = "" for val in data: txs += str(val) txs += ',' txs = txs[:-1] txs += ';' self.serial.write(txs.encode()) def ledControl(self, val): if val == 2: val = 1 self.serialSend([0, val]) def dSpeed(self, val): self.serialSend([1, self.speedSlider.value()]) def dServo(self, val): self.serialSend([2, self.servoSlider.value()]) def dFORWARD(self): self.serialSend([3, 1]) def dSTOP(self): self.serialSend([4, 1]) def dBACKWARD(self): self.serialSend([5, 1]) def ImageUpdateSlot(self, Image): self.QTcamera.setPixmap(QPixmap.fromImage(Image)) class Worker1(QThread): ImageUpdate = pyqtSignal(QImage) # 新增串口发送请求信号 SerialSendRequest = pyqtSignal(list) def run(self): self.ThreadActive = True Capture = cv2.VideoCapture(0) while self.ThreadActive: iSee = False controlXY = 0 success, frame = Capture.read() if success: height, width = frame.shape[0:2] hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) binary = cv2.inRange(hsv, (0, 100, 250), (20, 255, 255)) roi = cv2.bitwise_and(frame, frame, mask=binary) contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) if len(contours) != 0: maxcont = max(contours, key=cv2.contourArea) moments = cv2.moments(maxcont) if moments["m00"] > 20: cx = int(moments["m10"] / moments["m00"]) cy = int(moments["m01"] / moments["m00"]) iSee = True controlXY = 100 * (cx - width / 2) / width cv2.drawContours(frame, maxcont, -1, (0, 255, 0), 2) cv2.line(frame, (cx, 0), (cx, height), (0, 0, 255), 6) cv2.line(frame, (0, cy), (width, cy), (0, 255, 0), 6) cv2.putText(frame, 'iSee: {};'.format(iSee), (width - 370, height - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 0, 0), 1, cv2.LINE_AA) cv2.putText(frame, 'controlX: {:.2f}'.format(controlXY), (width - 200, height - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 0, 0), 1, cv2.LINE_AA) if iSee == True: # 替换直接调用,发送信号 self.SerialSendRequest.emit([2, controlXY]) ConvertToQtFormat = QImage(frame.data, frame.shape[1], frame.shape[0], QImage.Format_BGR888) Pic = ConvertToQtFormat.scaled(800, 600, Qt.KeepAspectRatio) self.ImageUpdate.emit(Pic) def stop(self): self.ThreadActive = False self.quit() if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) print(f"Класс Worker имеет{Worker1.__dict__}") print(f"Класс CustomMainWindow имеет{CustomMainWindow.__dict__}") mainWindow = CustomMainWindow() mainWindow.show() sys.exit(app.exec_())
额外修正:调整了
dFORWARD等按钮点击方法的参数定义,避免原代码中信号传递参数不匹配导致的报错。
内容的提问来源于stack exchange,提问作者Rauf 44
相关产品推荐
相关产品推荐

