为什么PyQt会在没有错误信息的情况下崩溃?(退出代码为0xC0000409)

9

我正在尝试使用PyQt开发软件,但经常遇到软件崩溃且没有调试信息(只有退出代码0xC0000409)。我正在使用QThread,并编写了以下系统:

class serialThreadC(QThread):
    updateOutBox = QtCore.pyqtSignal(str)
    updateStatus = QtCore.pyqtSignal(int)

    def __init__(self):
        super(serialThreadC, self).__init__()
        self.ser = False
        self.state = 0
        self.serialEnabled = False

    def run(self):
        while True:
            if self.state == -3 or self.state == -2:
                if self.SerialEnabled:
                    self.updatePB(20)
            elif self.state == 0:
                if self.serialEnabled:
                    self.updatePB(20)

    def ConnDisconn(self):
        self.serialEnabled = not self.serialEnabled

    def updatePB(self, stat):
        self.state = stat
        self.updateStatus.emit(self.state)

serialThread = serialThreadC()
serialThread.start()

## sw is a QDialog already loaded
serialThread.updateOutBox.connect(sw.updateOutBox)
serialThread.updateStatus.connect(sw.updateStatus)

sw.PB_ConnDisconn.clicked.connect(serialThread.ConnDisconn)

我在run()ConnDisconn()中读写serialEnabled时会崩溃。我知道PyQt不是线程安全的,变量处理错误会导致我的类型崩溃,但我无法理解我的代码有什么问题。我的想法(也许是错的)是所有serialThread方法都在同一个线程上执行,即使它们连接到gui(主线程)。这是错误的吗?同样地,我从serialThread发出事件,并将它们连接到GUI,但这从未给我带来问题。

您能看到我犯的错误吗?如果有崩溃而没有其他信息,有没有调试代码的方法?(我使用PyCharm 2017.1.3)。


7
你尝试过从终端运行吗? - eyllanesc
3
没错!在终端里我找到了导致崩溃的原因:|结果我白费了8个小时来调试代码,但却没有任何信息...在这种情况下,似乎Python无法理解我重载了两个类似的函数updatePB(self, stat)和updatePB(self),我在调用它时传递了2个参数而不是1个,从而导致了错误。 - brazoayeye
谢谢@eyllanesc!我试图使用PyCharm的运行/调试配置运行代码,但只收到了错误代码。 - Ledorub
2个回答

10

PyQt的线程安全程度与Qt的线程安全程度相同。Qt文档会告诉你哪些API部分是保证线程安全的,以及在什么情况下。

跨线程信号是线程安全的,所以在你的例子中调用updatePB方法是可以的。但是你的ConnDisconn方法不是线程安全的,但这与PyQt或Qt无关 - 这只是你编写方式的后果。serialEnabled属性可能被两个线程同时读/写,因此其行为是严格未定义的。编写此代码的线程安全方法是使用互斥锁,如下所示:

class serialThreadC(QThread):
    updateOutBox = QtCore.pyqtSignal(str)
    updateStatus = QtCore.pyqtSignal(int)

    def __init__(self):
        super(serialThreadC, self).__init__()
        self.ser = False
        self.state = 0
        self._mutex = QMutex()
        self.serialEnabled = False   

    def ConnDisconn(self):
        self._mutex.lock()
        self.serialEnabled = not self.serialEnabled
        self._mutex.unlock()

    def run(self):
        while True:
            if self.state == -3 or self.state == -2:
                self._mutex.lock()
                if self.serialEnabled:
                    self.updatePB(20)
                self._mutex.unlock()
            elif self.state == 0:
                self._mutex.lock()
                if self.serialEnabled:
                    self.updatePB(20)
                self._mutex.unlock()

注意:如果您正在使用任何类型的IDE或调试器,并且出现意外错误或崩溃,您诊断问题的第一步应该始终是在标准控制台中测试代码。很多时候,IDE或调试器本身可能是问题的原因,或者可能遮盖来自Python或底层库(如Qt)的错误消息。


我不明白重点在哪里。既然你说 ConnDisconn(self) 是一个不同于 serialThread 的线程(这很有道理,因为 ConnDisconn 与 GUI 信号相关联),那么对于 sw.updateStatus 应该也是一样的情况(它不在主线程中)。在 sw.updateStatus 中,我使用了 MyButton.setText(),这个操作可以吗?而且,不同线程之间并发读取变量也是被禁止的吗? - brazoayeye
@brazoayeye。我没有说ConnDisconn在不同的线程中(它不是,在主线程中运行,并由主线程调用)。但我确实说过updatePB是可以的,因为它发出了一个跨线程信号,这是由Qt保证线程安全的。因此,是的,由于这个直接结果,调用setText()是安全的。这在PyQt中都是标准的东西,在SO上有数十个类似的问题。从来没有禁止多个线程读/写任何东西 - 这就是整个问题所在!(这就是互斥锁的作用)。 - ekhumoro
@brazoayeye。我应该补充一点,针对你的具体示例,互斥锁可能是过度设计了,因为(我假设)只有一个线程会修改serialEnabled(即主线程)。所以你的示例代码看起来还可以,尽管它不是100%严格正确的。至于修复你的代码,我的答案中最重要的部分可能是底部的注释。其余部分只是试图回答你提出的更一般的问题。 - ekhumoro
如果 ConnDisconn 函数存在于主线程中,因为它通过信号槽与主线程调用,那么为什么 sw.updateStatus 函数不应该存在于 serialThread 中,因为它以非常相似的方式被 serialThread 调用?你能否发布一些相关的问题教程来深入这个主题? - brazoayeye
1
@brazoayeye。因为serialThread也在主线程中运行。QThread类本身并不是一个线程 - 它只是一个管理由操作系统提供的底层线程的类。因此,您的serialThread及其方法全部都在主线程中运行。您示例中唯一在主线程之外执行的部分是run方法中的代码。这会调用updatePB,该方法发出一个信号。Qt会自动检测到信号何时从一个线程发送到另一个线程,并以线程安全的方式进行处理(通过向接收线程的事件循环发布事件)。 - ekhumoro
我使用Anaconda终端运行代码,然后就发现了bug!谢谢@ekhumoro。 - Sourabh Desai

-1

我将所有的浮点变量设为整数值,并且它起作用了

之前


var1 = 10.0

var2 = 26.0

之后


var1 = 10

var2 = 26

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接