9

I'm trying to develop a software with PyQt, but I often get stuck on software crashes without debug information (only the exit code 0xC0000409). I'm using QThread, and I wrote a system like this:

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)

I have crashes when I read/write serialEnabled in run() or in ConnDisconn(). I know that PyQt is not thread-safe and that a wrong handling of variables gives crashes of my type, but I can't understand what is wrong with my code. My idea (maybe wrong) is that all serialThread methods are executed on the same thread, also if they are connected to a gui (main thread). Is that wrong? In the same way, I emit events from serialThread and I connected them to the GUI, but that never gave me problems.

Can you see the mistake I made? Is there a way to debug the code if there is a crash without other infos? (I use PyCharm 2017.1.3).

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
brazoayeye
  • 329
  • 1
  • 2
  • 14
  • 7
    Have you tried to run from the terminal? – eyllanesc Oct 12 '17 at 13:58
  • 3
    That's true! In the terminal i have the cause of the crash :| And i waste like 8hours to debug a code without informations... In that case it seems python can't understand the overload i did of two similar functions updatePB(self, stat) and updatePB(self), crying because i gave 2 parameters instead of 1 when i call it. – brazoayeye Oct 12 '17 at 15:28
  • Thanks @eyllanesc! I was trying to run code with PyCharm's run/debug configurations and got only error code. – Ledorub Apr 05 '21 at 14:05

2 Answers2

10

PyQt is thread-safe to the same extent that Qt is thread-safe. The Qt docs will tell you which parts of their API are guaranteed to be so, and under what circumstances.

Cross-thread signals are thread-safe, so calling the updatePB method in your example is okay. Your ConnDisconn method is not thread-safe, but that has got nothing to do with PyQt or Qt - it's just a consequence of how you wrote it. The serialEnabled attribute could be read/written by two threads simultaneously, so the behaviour is strictly undefined. A thread-safe way of writing this would be to use a mutex, like so:

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()

(NB: if you're using any kind of IDE or debugger, and you are getting unexpected errors or crashes, your first step in diagnosing the problem should always be to test the code in a standard console. Quite often, the IDE or debugger itself can be the cause of the problem, or may mask error messages comming either from Python or from underlying libraries, such as Qt).

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • I don't get the point. Since you told that the `ConnDisconn(self)` is a different thread than `serialThread` (that makes sense, `ConnDisconn` is connected to a GUI signal) the same should be for `sw.updateStatus` (that's not in the main thread). In the `sw.updateStatus` I `MyButton.setText()`, is this operation allowed? And more, is it also forbidden to read variables concurrently using different threads? – brazoayeye Oct 12 '17 at 16:54
  • @brazoayeye. I did not say that `ConnDisconn` is in a different thread (and it isn't, it lives in the main thread, and is called by the main thread). But I did say that `updatePB` is okay, because it emits a cross-thread signal, which is guaranteed (by Qt) to be thread-safe. So yes, as a direct consequence of that, it is safe to call `setText()`. This is all standard stuff in PyQt, and there are dozens of similar questions about it on SO. It is never *forbidden* to read/write *anything* by multiple threads - that is the whole problem! (And that is what the mutex is for). – ekhumoro Oct 12 '17 at 17:14
  • @brazoayeye. I should add, though, that for your specific example, the mutex is probably overkill, because (I assume) there is only ever one thread that modifies `serialEnabled` (i.e. the main thread). So your example code looks okay, even though it is not 100% strictly correct. As far as fixing your code is concerned, the most important part of my answer is probably the note at the bottom. The rest just tries to answer the more general questions you asked. – ekhumoro Oct 12 '17 at 17:28
  • if `ConnDisconn` lives in the main thread because it's called by the main thread with signal-slots, why doesn't `sw.updateStatus` lives in the `serialThread` since it's called by `serialThread` in a mostly identical way? Can you post most relevant questions-tutorial to deepen the topic? – brazoayeye Oct 12 '17 at 17:37
  • 1
    @brazoayeye. Because `serialThread` also lives in the main thread. The `QThread` class is not itself a thread - it is merely a class which *manages* an underlying thread provided by the OS. So your `serialThread` and its methods **all** live in the main thread. The only part of your example that is executed outside the main thread is the code in the `run` method. This calls `updatePB`, which emits a signal. Qt automatically detects when a signal is sent from one thread to another, and so does it in a thread-safe way (by posting an event to the event-loop of the receiving thread). – ekhumoro Oct 12 '17 at 17:49
  • I used anaconda terminal to run the code and voila, I spotted the bug! Thanks @ekhumoro – Sourabh Desai Jan 28 '21 at 05:26
-1

I set all float variables to int values and it worked

before


var1 = 10.0

var2 = 26.0

after


var1 = 10

var2 = 26
j__carlson
  • 1,346
  • 3
  • 12
  • 20