Skip to content

Instantly share code, notes, and snippets.

@jazzycamel
Last active March 29, 2023 21:49
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jazzycamel/8abd37bf2d60cce6e01d to your computer and use it in GitHub Desktop.
Save jazzycamel/8abd37bf2d60cce6e01d to your computer and use it in GitHub Desktop.
Simple example of the correct way to use (Py)Qt(5) and QThread
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from itertools import count, islice
class Threaded(QObject):
result=pyqtSignal(int)
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
@pyqtSlot()
def start(self): print("Thread started")
@pyqtSlot(int)
def calculatePrime(self, n):
primes=(n for n in count(2) if all(n % d for d in range(2, n)))
self.result.emit(list(islice(primes, 0, n))[-1])
class GUI(QWidget):
requestPrime=pyqtSignal(int)
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
self._thread=QThread()
self._threaded=Threaded(result=self.displayPrime)
self.requestPrime.connect(self._threaded.calculatePrime)
self._thread.started.connect(self._threaded.start)
self._threaded.moveToThread(self._thread)
qApp.aboutToQuit.connect(self._thread.quit)
self._thread.start()
l=QVBoxLayout(self)
self._iterationLE=QLineEdit(self, placeholderText="Iteration (n)")
l.addWidget(self._iterationLE)
self._requestBtn=QPushButton(
"Calculate Prime", self, clicked=self.primeRequested)
l.addWidget(self._requestBtn)
self._busy=QProgressBar(self)
l.addWidget(self._busy)
self._resultLbl=QLabel("Result:", self)
l.addWidget(self._resultLbl)
@pyqtSlot()
def primeRequested(self):
try: n=int(self._iterationLE.text())
except: return
self.requestPrime.emit(n)
self._busy.setRange(0,0)
self._iterationLE.setEnabled(False)
self._requestBtn.setEnabled(False)
@pyqtSlot(int)
def displayPrime(self, prime):
self._resultLbl.setText("Result: {}".format(prime))
self._busy.setRange(0,100)
self._iterationLE.setEnabled(True)
self._requestBtn.setEnabled(True)
if __name__=="__main__":
from sys import exit, argv
a=QApplication(argv)
g=GUI()
g.show()
exit(a.exec_())
@bjones1
Copy link

bjones1 commented Feb 15, 2016

Rob, a quick question. I saw your helpful post of PyQt and read through your code. I'm unfamiliar with the use of result=self.displayPrime on line 26. I assume this is the equivalent of self.result.connect(GUI.displayPrime), but wondered if you had a hyperlink so I could read more about this (nice) syntax?

Thanks!

@jazzycamel
Copy link
Author

Its the equivalent of self._threaded.result.connect(self.displayPrime), i.e. you pass a reference to the instance of GUI.displayPrime.

I don't have a hyperlink anywhere, but you can basically pass the name of any objects signal to its constructor as a keyword argument with a reference to an instance of the target object's slot.

@serg06
Copy link

serg06 commented Feb 28, 2021

Fun fact: If using PySide2 instead of PyQt5, start() is called on the UI thread instead of the worker thread.

Workaround: Use this thread class instead of the original:

class QThread2(QThread):
	started2 = Signal()

	def __init__(self):
		QThread.__init__(self)
		self.started.connect(self.onStarted)

	def onStarted(self):
		self.started2.emit()

@jazzycamel
Copy link
Author

Fun fact: If using PySide2 instead of PyQt5, start() is called on the UI thread instead of the worker thread.

Workaround: Use this thread class instead of the original:

class QThread2(QThread):
	started2 = Signal()

	def __init__(self):
		QThread.__init__(self)
		self.started.connect(self.onStarted)

	def onStarted(self):
		self.started2.emit()

@serg06 Works fine in PySide2 for me (without your 'workaround') with the following import adaptations for PySide2:

from PySide2.QtCore import *
from PySide2.QtWidgets import *

pyqtSignal=Signal
pyqtSlot=Slot

from itertools import count, islice

class Threaded(QObject):
    result=pyqtSignal(int)

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)

    @pyqtSlot()
    def start(self): print("Thread started")

    @pyqtSlot(int)
    def calculatePrime(self, n):
        primes=(n for n in count(2) if all(n % d for d in range(2, n)))
        self.result.emit(list(islice(primes, 0, n))[-1])

class GUI(QWidget):
    requestPrime=pyqtSignal(int)

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)

        self._thread=QThread()
        self._threaded=Threaded()
        self._threaded.result.connect(self.displayPrime)
        self.requestPrime.connect(self._threaded.calculatePrime)
        self._thread.started.connect(self._threaded.start)
        self._threaded.moveToThread(self._thread)
        qApp.aboutToQuit.connect(self._thread.quit)
        self._thread.start()

        l=QVBoxLayout(self)
        self._iterationLE=QLineEdit(self, placeholderText="Iteration (n)")
        l.addWidget(self._iterationLE)
        self._requestBtn=QPushButton(
            "Calculate Prime", self, clicked=self.primeRequested)
        l.addWidget(self._requestBtn)
        self._busy=QProgressBar(self)
        l.addWidget(self._busy)
        self._resultLbl=QLabel("Result:", self)
        l.addWidget(self._resultLbl)

    @pyqtSlot()
    def primeRequested(self):
        try: n=int(self._iterationLE.text())
        except: return
        self.requestPrime.emit(n)
        self._busy.setRange(0,0)
        self._iterationLE.setEnabled(False)
        self._requestBtn.setEnabled(False)

    @pyqtSlot(int)
    def displayPrime(self, prime):
        self._resultLbl.setText("Result: {}".format(prime))
        self._busy.setRange(0,100)
        self._iterationLE.setEnabled(True)
        self._requestBtn.setEnabled(True)

if __name__=="__main__":
    from sys import exit, argv

    a=QApplication(argv)
    g=GUI()
    g.show()
    exit(a.exec_())

I tested this using Python 3.8.5 and PySide5.15.2 on Ubuntu 20.04.2LTS. What versions/OS are you running and how did you determine which thread code was running in?

Did you test my code or your own code using this method? There are a couple of gotchas with QThread; for example, if the object you move to the thread with QThread.moveToThread has a parent, then it will not actually get moved, but will not fail either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment