You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5 QThread中调用pyserial出现ClearCommError句柄无效错误求助

问题根因

你遇到的串口句柄无效报错,核心原因是Windows系统下串口句柄具有线程亲和性:你在主线程中初始化并打开了全局serialPort对象,后续在子线程中直接调用该对象的in_waiting、read()等方法属于跨线程操作串口资源,系统会直接拒绝访问抛出错误。单线程场景下所有串口操作都在同一个线程执行,因此运行正常。

修复方案

核心原则是所有串口相关操作(初始化、打开、读、写、关闭)全部放在同一个线程中执行,主线程和串口线程之间仅通过Qt信号槽传递指令和数据,禁止跨线程直接操作串口对象。
修改点如下:

  • 移除全局串口对象,将所有串口逻辑封装到Worker线程类内部
  • 新增Qt信号实现主线程向串口线程传递发送指令、配置串口参数的请求
  • 新增线程退出标志,避免程序退出时子线程死锁
  • 按钮点击事件通过信号触发串口线程的发送逻辑,避免跨线程写串口
修改后的完整代码
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication,QWidget,QMainWindow
from PyQt5.QtCore import QCoreApplication, QObject, pyqtSignal, pyqtSlot, QThread
import serial
from GUI import Ui_MainGUIWindow
import sys

class Worker(QObject):
    finished = pyqtSignal()
    dataReady = pyqtSignal(str)
    # 新增接收发送指令的槽函数触发信号
    send_cmd = pyqtSignal(str)
    
    def __init__(self, port, baud_rate):
        super().__init__()
        self.port = port
        self.baud_rate = baud_rate
        self.serial_port = None
        self.running_flag = True # 线程退出标志

    @pyqtSlot()
    def run(self):
        # 串口初始化、打开全部在子线程执行
        self.serial_port = serial.Serial(
            port = self.port, 
            baudrate=self.baud_rate, 
            bytesize=8, 
            timeout=None, 
            stopbits=serial.STOPBITS_ONE
        )
        if not self.serial_port.isOpen():
            self.serial_port.open()
        # 绑定发送指令的信号槽
        self.send_cmd.connect(self.write_serial)
        # 读串口循环
        while self.running_flag:
            if self.serial_port.in_waiting > 0:
                serial_string = self.serial_port.read()
                self.dataReady.emit(serial_string.decode('Ascii'))
        # 线程退出前关闭串口
        self.serial_port.close()
        self.finished.emit()
    
    @pyqtSlot(str)
    def write_serial(self, cmd):
        if self.serial_port and self.serial_port.isOpen():
            self.serial_port.write(f"{cmd}\r".encode())
    
    def stop(self):
        self.running_flag = False

            
class GUI(QWidget):
    def __init__(self):
        super().__init__()
        # 初始化Worker时传入串口参数,不要在主线程初始化串口
        self.obj = Worker(port="COM20", baud_rate=9600)
        self.thread = QThread()
        self.obj.dataReady.connect(self.onDataReady)
        self.obj.moveToThread(self.thread)
        self.obj.finished.connect(self.thread.quit)
        self.thread.started.connect(self.obj.run)
        self.thread.finished.connect(app.exit)
        self.thread.start()
        self.initUI()

    def initUI(self):
        self.window = QMainWindow()
        self.form = Ui_MainGUIWindow() 
        self.form.setupUi(self.window)
        # 按钮点击时发信号给Worker线程执行发送操作
        self.form.FVButton.clicked.connect(lambda: self.obj.send_cmd.emit("fv"))
        self.window.show()
        
    @pyqtSlot(str)
    def onDataReady(self, serial_string):
        self.form.plainTextOutput.insertPlainText(serial_string)
        self.form.plainTextOutput.ensureCursorVisible()
    
    def closeEvent(self, event):
        # 窗口关闭时先停止Worker线程
        self.obj.stop()
        self.thread.wait()
        event.accept()
         

if __name__ == '__main__':
    app = QApplication(sys.argv)
    form = GUI()
    app.exec_()
    sys.exit()
Spyder内核崩溃问题解决

该问题是Spyder内置IPython内核的兼容性限制,内核对PyQt事件循环的管理存在缺陷,多次运行PyQt程序后残留的事件循环实例会导致内核崩溃,和你的代码逻辑无关。解决方案如下:

  • 开发调试阶段每次运行代码前手动重启Spyder内核
  • 正式运行时直接通过Anaconda命令提示符执行脚本,你当前已经验证该方式可正常运行

内容的提问来源于stack exchange,提问作者Nirmala

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 00:15:03