1

I wrote simple program which has pyQt interface with 2 buttons (start and cancel). Start button runs some calculations in the background (by starting update function) and thanks to threading I can still use UI. But the application crashes after 10sec - 2 minutes. UI just dissapears, program shutdown.

when I use pythonw to run app without console thread crashes after ~25 sec but gui still works.

#!/usr/bin/python
import threading
import sys
from PyQt4 import QtGui, QtCore
import time
import os


class Class(QtGui.QWidget):

    def __init__(self):
        #Some init variables
        self.initUI()

    def initUI(self):
        #some UI
        self.show()

    def update(self,stop_event):
            while True and not stop_event.isSet():
                self.updateSpeed()
                self.updateDistance()
                self.printLogs()
                self.saveCSV()
                self.guiUpdate()
                time.sleep(1)

    #gui button function
    def initiate(self):
        self.stop_event = threading.Event()
        self.c_thread = threading.Thread(target = self.update, args=(self.stop_event,))
        self.c_thread.start()

    #Also gui button function
    def cancelTracking(self):
        self.stop_event.set()
        self.close()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Class()
    sys.exit(app.exec_())
    ex.update()

if __name__ == '__main__':
    main()

I dont know if I'm doing threading right. I found example like this on stack. I'm quite new to python and I'm using threading for the first time.

Karmel
  • 115
  • 3
  • 10

2 Answers2

6

It is most likely due to calling a GUI function in your separate thread. PyQt GUI calls like setText() on a QLineEdit are not allowed from a thread. Anything that has PyQt painting outside of the main thread will not work. One way to get around this is to have your thread emit a signal to update the GUI when data is ready. The other way is to have a timer periodically checking for new data and updating the paintEvent after a certain time.

========== EDIT ==========

To Fix this issue I created a library named qt_thread_updater. https://github.com/justengel/qt_thread_updater This works by continuously running a QTimer. When you call call_latest the QTimer will run the function in the main thread.

from qt_thread_updater import get_updater

lbl = QtWidgets.QLabel('Value: 1')

counter = {'a': 1}

def run_thread():
    while True:
        text = 'Value: {}'.format(counter['a'])
        get_updater().call_latest(lbl.setText, text)
        counter['a'] += 1
        time.sleep(0.1)

th = threading.Thread(target=run_thread)
th.start()

========== END EDIT ==========

#!/usr/bin/python
import threading
import sys
from PyQt4 import QtGui, QtCore
import time
import os


class Class(QtGui.QWidget):

    display_update = QtCore.pyqtSignal() # ADDED

    def __init__(self):
        #Some init variables
        self.initUI()

    def initUI(self):
        #some UI
        self.display_update.connect(self.guiUpdate) # ADDED
        self.show()

    def update(self):
        while True and not self.stop_event.isSet():
            self.updateSpeed()
            self.updateDistance()
            self.printLogs()
            self.saveCSV()

            # self.guiUpdate()
            self.display_update.emit() # ADDED

            time.sleep(1)

    #gui button function
    def initiate(self):
        self.stop_event = threading.Event()
        self.c_thread = threading.Thread(target = self.update)
        self.c_thread.start()

    #Also gui button function
    def cancelTracking(self):
        self.stop_event.set()
        self.close()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Class()
    sys.exit(app.exec_())
    # ex.update() # - this does nothing

if __name__ == '__main__':
    main()

The other thing that could be happening is deadlock from two threads trying to access the same variable. I've read that this shouldn't be possible in python, but I have experienced it from the combination of PySide and other Python C extension libraries.

May also want to join the thread on close or use the QtGui.QApplication.aboutToQuit signal to join the thread before the program closes.

justengel
  • 6,132
  • 4
  • 26
  • 42
3

The Qt documentation for QThreads provides two popular patterns for using threading. You can either subclass QThread (the old way), or you can use the Worker Model, where you create a custom QObject with your worker functions and run them in a separate QThread.

In either case, you can't directly update the GUI from the background thread, so in your update function, the guiUpdate call will most likely crash Qt if it tries to change any of the GUI elements.

The proper way to run background processes is to use one of the two QThread patterns and communicate with the main GUI thread via Signals and Slots.

Also, in the following bit of code,

app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
ex.update()

app.exec_ starts the event loop and will block until Qt exits. Python won't run the ex.update() command until Qt has exited and the ex window has already been deleted, so you should just delete that command.

Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
  • ou, yes. I forgot about ex.update(). I use this fuction from GUI button now so I will delete it. Also thanks for the tips! – Karmel Mar 15 '16 at 23:35