4
votes

I'm trying to create a simple threaded application whereby i have a method which does some long processing and a widget that displays a loading bar and cancel button.

My problem is that no matter how i implement the threading it doesn't actually thread - the UI is locked up once the thread kicks in. I've read every tutorial and post about this and i'm now resorting on asking the community to try and solve my problem as i'm at a loss!

Initially i tried subclassing QThread until the internet said this was wrong. I then attempted the moveToThread approach but it made zero difference.

Initialization code:

loadingThreadObject = LoadThread(arg1)
loadingThread = PythonThread()
loadingThreadObject.moveToThread(loadingThread)
loadingThread.started.connect(loadingThreadObject.load)
loadingThread.start()

PythonThread class (apparently QThreads are bugged in pyQt and don't start unless you do this):

class PythonThread (QtCore.QThread):
    def __init__(self, parent=None):
        QtCore.QThread.__init__(self, parent)

    def start(self):
        QtCore.QThread.start(self)

    def run(self):
        QtCore.QThread.run(self)

LoadThread class:

class LoadThread (QtCore.QObject):
    results = QtCore.Signal(tuple)

    def __init__ (self, arg):
         # Init QObject
         super(QtCore.QObject, self).__init__()

         # Store the argument
         self.arg = arg

    def load (self):
         #
         # Some heavy lifting is done
         #

         loaded = True
         errors = []

         # Emits the results
         self.results.emit((loaded, errors))

Any help is greatly appreciated!

Thanks. Ben.

2
What, exactly, is the nature of the "heavy lifting"? The answer to this could have a bearing on whether threading will provide any benefit (because of the limitations imposed by python's GIL). - ekhumoro
It mostly involves SQL queries. I'm not so interested in performance benefits as a responsive UI though. - Ben
I wasn't talking about performance. If the GIL is not released during the long-running task, then, in itself, threading will not prevent blocking. You need to break the task up into chunks and periodically send a signal to the main GUI thread so that it can process any pending events (i.e. call qApp.processEvents() or something). - ekhumoro
Fair enough, so does running an SQL query lock up the GIL? If so i'm kind of screwed since a query can take anywhere up to a few minutes to run... - Ben
I have very little knowledge of SQL, but it seems likely that the GIL would be released whenever possible. I suppose it could depend on the specific SQL library you are using, though. - ekhumoro

2 Answers

2
votes

The problem was with the SQL library I was using (a custom in-house solution) which turned out not to be thread safe and thus performed blocking queries.

If you are having a similar problem, first try removing the SQL calls and seeing if it still blocks. If that solves the blocking issue, try reintroducing your queries using raw SQL via MySQLdb (or the equivalent for the type of DB you're using). This will diagnose whether or not the problem is with your choice of SQL library.

1
votes

The function connected to the started signal will run the thread which it was connected, the main GUI thread. However, a QThread's start() function executes its run() method in the thread after the thread is initialized so a subclass of QThread should be created and its run method should run LoadThread.load, the function you want to execute. Don't inherit from PythonThread, there's no need for that. The QThread subclass's start() method should be used to start the thread.

PS: Since in this case the subclass of QThread's run() method only calls LoadThread.load(), the run() method could be simply set to LoadThread.load:

class MyThread(QtCore.QThread):
    run = LoadThread.load # x = y in the class block sets the class's x variable to y

An example:

import time
from PyQt4 import QtCore, QtGui
import sys
application = QtGui.QApplication(sys.argv)

class LoadThread (QtCore.QObject):
    results = QtCore.pyqtSignal(tuple)

    def __init__ (self, arg):
         # Init QObject
         super(QtCore.QObject, self).__init__()

         # Store the argument
         self.arg = arg

    def load(self):
         #
         # Some heavy lifting is done
         #
         time.sleep(5)
         loaded = True
         errors = []

         # Emits the results
         self.results.emit((loaded, errors))

l = LoadThread("test")

class MyThread(QtCore.QThread):
    run = l.load

thread = MyThread()

button = QtGui.QPushButton("Do 5 virtual push-ups")
button.clicked.connect(thread.start)
button.show()
l.results.connect(lambda:button.setText("Phew! Push ups done"))
application.exec_()