Related
I have a program which interfaces with a radio I am using via a gui I wrote in PyQt. Obviously one of the main functions of the radio is to transmit data, but to do this continuously, I have to loop the writes, which causes the gui to hang. Since I have never dealt with threading, I tried to get rid of these hangs using QCoreApplication.processEvents(). The radio needs to sleep between transmissions, though, so the gui still hangs based on how long these sleeps last.
Is there a simple way to fix this using QThread? I have looked for tutorials on how to implement multithreading with PyQt, but most of them deal with setting up servers and are much more advanced than I need them to be. I honestly don't even really need my thread to update anything while it is running, I just need to start it, have it transmit in the background, and stop it.
I created a little example that shows 3 different and simple ways of dealing with threads. I hope it will help you find the right approach to your problem.
import sys
import time
from PyQt5.QtCore import (QCoreApplication, QObject, QRunnable, QThread,
QThreadPool, pyqtSignal)
# Subclassing QThread
# http://qt-project.org/doc/latest/qthread.html
class AThread(QThread):
def run(self):
count = 0
while count < 5:
time.sleep(1)
print("A Increasing")
count += 1
# Subclassing QObject and using moveToThread
# http://blog.qt.digia.com/blog/2007/07/05/qthreads-no-longer-abstract
class SomeObject(QObject):
finished = pyqtSignal()
def long_running(self):
count = 0
while count < 5:
time.sleep(1)
print("B Increasing")
count += 1
self.finished.emit()
# Using a QRunnable
# http://qt-project.org/doc/latest/qthreadpool.html
# Note that a QRunnable isn't a subclass of QObject and therefore does
# not provide signals and slots.
class Runnable(QRunnable):
def run(self):
count = 0
app = QCoreApplication.instance()
while count < 5:
print("C Increasing")
time.sleep(1)
count += 1
app.quit()
def using_q_thread():
app = QCoreApplication([])
thread = AThread()
thread.finished.connect(app.exit)
thread.start()
sys.exit(app.exec_())
def using_move_to_thread():
app = QCoreApplication([])
objThread = QThread()
obj = SomeObject()
obj.moveToThread(objThread)
obj.finished.connect(objThread.quit)
objThread.started.connect(obj.long_running)
objThread.finished.connect(app.exit)
objThread.start()
sys.exit(app.exec_())
def using_q_runnable():
app = QCoreApplication([])
runnable = Runnable()
QThreadPool.globalInstance().start(runnable)
sys.exit(app.exec_())
if __name__ == "__main__":
#using_q_thread()
#using_move_to_thread()
using_q_runnable()
Take this answer updated for PyQt5, python 3.4
Use this as a pattern to start a worker that does not take data and return data as they are available to the form.
1 - Worker class is made smaller and put in its own file worker.py for easy memorization and independent software reuse.
2 - The main.py file is the file that defines the GUI Form class
3 - The thread object is not subclassed.
4 - Both thread object and the worker object belong to the Form object
5 - Steps of the procedure are within the comments.
# worker.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int)
#pyqtSlot()
def procCounter(self): # A slot takes no params
for i in range(1, 100):
time.sleep(1)
self.intReady.emit(i)
self.finished.emit()
And the main file is:
# main.py
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout
import sys
import worker
class Form(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("0")
# 1 - create Worker and Thread inside the Form
self.obj = worker.Worker() # no parent!
self.thread = QThread() # no parent!
# 2 - Connect Worker`s Signals to Form method slots to post data.
self.obj.intReady.connect(self.onIntReady)
# 3 - Move the Worker object to the Thread object
self.obj.moveToThread(self.thread)
# 4 - Connect Worker Signals to the Thread slots
self.obj.finished.connect(self.thread.quit)
# 5 - Connect Thread started signal to Worker operational slot method
self.thread.started.connect(self.obj.procCounter)
# * - Thread finished signal will close the app if you want!
#self.thread.finished.connect(app.exit)
# 6 - Start the thread
self.thread.start()
# 7 - Start the form
self.initUI()
def initUI(self):
grid = QGridLayout()
self.setLayout(grid)
grid.addWidget(self.label,0,0)
self.move(300, 150)
self.setWindowTitle('thread test')
self.show()
def onIntReady(self, i):
self.label.setText("{}".format(i))
#print(i)
app = QApplication(sys.argv)
form = Form()
sys.exit(app.exec_())
According to the Qt developers, subclassing QThread is incorrect (see http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/). But that article is really hard to understand (plus the title is a bit condescending). I found a better blog post that gives a more detailed explanation about why you should use one style of threading over another: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
Also, I would highly recommend this video from KDAB on signals and slots between threads.
In my opinion, you should probably never subclass thread with the intent to overload the run method. While that does work, you're basically circumventing how Qt wants you to work. Plus you'll miss out on things like events and proper thread safe signals and slots. Plus as you'll likely see in the above blog post, the "correct" way of threading forces you to write more testable code.
Here's a couple of examples of how to take advantage of QThreads in PyQt (I posted a separate answer below that properly uses QRunnable and incorporates signals/slots, that answer is better if you have a lot of async tasks that you need to load balance).
import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt
# very testable class (hint: you can use mock.Mock for the signals)
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
dataReady = QtCore.pyqtSignal(list, dict)
#QtCore.pyqtSlot()
def processA(self):
print "Worker.processA()"
self.finished.emit()
#QtCore.pyqtSlot(str, list, list)
def processB(self, foo, bar=None, baz=None):
print "Worker.processB()"
for thing in bar:
# lots of processing...
self.dataReady.emit(['dummy', 'data'], {'dummy': ['data']})
self.finished.emit()
class Thread(QtCore.QThread):
"""Need for PyQt4 <= 4.6 only"""
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
# this class is solely needed for these two methods, there
# appears to be a bug in PyQt 4.6 that requires you to
# explicitly call run and start from the subclass in order
# to get the thread to actually start an event loop
def start(self):
QtCore.QThread.start(self)
def run(self):
QtCore.QThread.run(self)
app = QtGui.QApplication(sys.argv)
thread = Thread() # no parent!
obj = Worker() # no parent!
obj.moveToThread(thread)
# if you want the thread to stop after the worker is done
# you can always call thread.start() again later
obj.finished.connect(thread.quit)
# one way to do it is to start processing as soon as the thread starts
# this is okay in some cases... but makes it harder to send data to
# the worker object from the main gui thread. As you can see I'm calling
# processA() which takes no arguments
thread.started.connect(obj.processA)
thread.start()
# another way to do it, which is a bit fancier, allows you to talk back and
# forth with the object in a thread safe way by communicating through signals
# and slots (now that the thread is running I can start calling methods on
# the worker object)
QtCore.QMetaObject.invokeMethod(obj, 'processB', Qt.QueuedConnection,
QtCore.Q_ARG(str, "Hello World!"),
QtCore.Q_ARG(list, ["args", 0, 1]),
QtCore.Q_ARG(list, []))
# that looks a bit scary, but its a totally ok thing to do in Qt,
# we're simply using the system that Signals and Slots are built on top of,
# the QMetaObject, to make it act like we safely emitted a signal for
# the worker thread to pick up when its event loop resumes (so if its doing
# a bunch of work you can call this method 10 times and it will just queue
# up the calls. Note: PyQt > 4.6 will not allow you to pass in a None
# instead of an empty list, it has stricter type checking
app.exec_()
# Without this you may get weird QThread messages in the shell on exit
app.deleteLater()
Very nice example from Matt, I fixed the typo and also pyqt4.8 is common now so I removed the dummy class as well and added an example for the dataReady signal
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import Qt
# very testable class (hint: you can use mock.Mock for the signals)
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
dataReady = QtCore.pyqtSignal(list, dict)
#QtCore.pyqtSlot()
def processA(self):
print "Worker.processA()"
self.finished.emit()
#QtCore.pyqtSlot(str, list, list)
def processB(self, foo, bar=None, baz=None):
print "Worker.processB()"
for thing in bar:
# lots of processing...
self.dataReady.emit(['dummy', 'data'], {'dummy': ['data']})
self.finished.emit()
def onDataReady(aList, aDict):
print 'onDataReady'
print repr(aList)
print repr(aDict)
app = QtGui.QApplication(sys.argv)
thread = QtCore.QThread() # no parent!
obj = Worker() # no parent!
obj.dataReady.connect(onDataReady)
obj.moveToThread(thread)
# if you want the thread to stop after the worker is done
# you can always call thread.start() again later
obj.finished.connect(thread.quit)
# one way to do it is to start processing as soon as the thread starts
# this is okay in some cases... but makes it harder to send data to
# the worker object from the main gui thread. As you can see I'm calling
# processA() which takes no arguments
thread.started.connect(obj.processA)
thread.finished.connect(app.exit)
thread.start()
# another way to do it, which is a bit fancier, allows you to talk back and
# forth with the object in a thread safe way by communicating through signals
# and slots (now that the thread is running I can start calling methods on
# the worker object)
QtCore.QMetaObject.invokeMethod(obj, 'processB', Qt.QueuedConnection,
QtCore.Q_ARG(str, "Hello World!"),
QtCore.Q_ARG(list, ["args", 0, 1]),
QtCore.Q_ARG(list, []))
# that looks a bit scary, but its a totally ok thing to do in Qt,
# we're simply using the system that Signals and Slots are built on top of,
# the QMetaObject, to make it act like we safely emitted a signal for
# the worker thread to pick up when its event loop resumes (so if its doing
# a bunch of work you can call this method 10 times and it will just queue
# up the calls. Note: PyQt > 4.6 will not allow you to pass in a None
# instead of an empty list, it has stricter type checking
app.exec_()
In PyQt there are a lot of options for getting asynchronous behavior. For things that need event processing (ie. QtNetwork, etc) you should use the QThread example I provided in my other answer on this thread. But for the vast majority of your threading needs, I think this solution is far superior than the other methods.
The advantage of this is that the QThreadPool schedules your QRunnable instances as tasks. This is similar to the task pattern used in Intel's TBB. It's not quite as elegant as I like but it does pull off excellent asynchronous behavior.
This allows you to utilize most of the threading power of Qt in Python via QRunnable and still take advantage of signals and slots. I use this same code in several applications, some that make hundreds of asynchronous REST calls, some that open files or list directories, and the best part is using this method, Qt task balances the system resources for me.
import time
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt
def async(method, args, uid, readycb, errorcb=None):
"""
Asynchronously runs a task
:param func method: the method to run in a thread
:param object uid: a unique identifier for this task (used for verification)
:param slot updatecb: the callback when data is receieved cb(uid, data)
:param slot errorcb: the callback when there is an error cb(uid, errmsg)
The uid option is useful when the calling code makes multiple async calls
and the callbacks need some context about what was sent to the async method.
For example, if you use this method to thread a long running database call
and the user decides they want to cancel it and start a different one, the
first one may complete before you have a chance to cancel the task. In that
case, the "readycb" will be called with the cancelled task's data. The uid
can be used to differentiate those two calls (ie. using the sql query).
:returns: Request instance
"""
request = Request(method, args, uid, readycb, errorcb)
QtCore.QThreadPool.globalInstance().start(request)
return request
class Request(QtCore.QRunnable):
"""
A Qt object that represents an asynchronous task
:param func method: the method to call
:param list args: list of arguments to pass to method
:param object uid: a unique identifier (used for verification)
:param slot readycb: the callback used when data is receieved
:param slot errorcb: the callback used when there is an error
The uid param is sent to your error and update callbacks as the
first argument. It's there to verify the data you're returning
After created it should be used by invoking:
.. code-block:: python
task = Request(...)
QtCore.QThreadPool.globalInstance().start(task)
"""
INSTANCES = []
FINISHED = []
def __init__(self, method, args, uid, readycb, errorcb=None):
super(Request, self).__init__()
self.setAutoDelete(True)
self.cancelled = False
self.method = method
self.args = args
self.uid = uid
self.dataReady = readycb
self.dataError = errorcb
Request.INSTANCES.append(self)
# release all of the finished tasks
Request.FINISHED = []
def run(self):
"""
Method automatically called by Qt when the runnable is ready to run.
This will run in a separate thread.
"""
# this allows us to "cancel" queued tasks if needed, should be done
# on shutdown to prevent the app from hanging
if self.cancelled:
self.cleanup()
return
# runs in a separate thread, for proper async signal/slot behavior
# the object that emits the signals must be created in this thread.
# Its not possible to run grabber.moveToThread(QThread.currentThread())
# so to get this QObject to properly exhibit asynchronous
# signal and slot behavior it needs to live in the thread that
# we're running in, creating the object from within this thread
# is an easy way to do that.
grabber = Requester()
grabber.Loaded.connect(self.dataReady, Qt.QueuedConnection)
if self.dataError is not None:
grabber.Error.connect(self.dataError, Qt.QueuedConnection)
try:
result = self.method(*self.args)
if self.cancelled:
# cleanup happens in 'finally' statement
return
grabber.Loaded.emit(self.uid, result)
except Exception as error:
if self.cancelled:
# cleanup happens in 'finally' statement
return
grabber.Error.emit(self.uid, unicode(error))
finally:
# this will run even if one of the above return statements
# is executed inside of the try/except statement see:
# https://docs.python.org/2.7/tutorial/errors.html#defining-clean-up-actions
self.cleanup(grabber)
def cleanup(self, grabber=None):
# remove references to any object or method for proper ref counting
self.method = None
self.args = None
self.uid = None
self.dataReady = None
self.dataError = None
if grabber is not None:
grabber.deleteLater()
# make sure this python obj gets cleaned up
self.remove()
def remove(self):
try:
Request.INSTANCES.remove(self)
# when the next request is created, it will clean this one up
# this will help us avoid this object being cleaned up
# when it's still being used
Request.FINISHED.append(self)
except ValueError:
# there might be a race condition on shutdown, when shutdown()
# is called while the thread is still running and the instance
# has already been removed from the list
return
#staticmethod
def shutdown():
for inst in Request.INSTANCES:
inst.cancelled = True
Request.INSTANCES = []
Request.FINISHED = []
class Requester(QtCore.QObject):
"""
A simple object designed to be used in a separate thread to allow
for asynchronous data fetching
"""
#
# Signals
#
Error = QtCore.pyqtSignal(object, unicode)
"""
Emitted if the fetch fails for any reason
:param unicode uid: an id to identify this request
:param unicode error: the error message
"""
Loaded = QtCore.pyqtSignal(object, object)
"""
Emitted whenever data comes back successfully
:param unicode uid: an id to identify this request
:param list data: the json list returned from the GET
"""
NetworkConnectionError = QtCore.pyqtSignal(unicode)
"""
Emitted when the task fails due to a network connection error
:param unicode message: network connection error message
"""
def __init__(self, parent=None):
super(Requester, self).__init__(parent)
class ExampleObject(QtCore.QObject):
def __init__(self, parent=None):
super(ExampleObject, self).__init__(parent)
self.uid = 0
self.request = None
def ready_callback(self, uid, result):
if uid != self.uid:
return
print "Data ready from %s: %s" % (uid, result)
def error_callback(self, uid, error):
if uid != self.uid:
return
print "Data error from %s: %s" % (uid, error)
def fetch(self):
if self.request is not None:
# cancel any pending requests
self.request.cancelled = True
self.request = None
self.uid += 1
self.request = async(slow_method, ["arg1", "arg2"], self.uid,
self.ready_callback,
self.error_callback)
def slow_method(arg1, arg2):
print "Starting slow method"
time.sleep(1)
return arg1 + arg2
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
obj = ExampleObject()
dialog = QtGui.QDialog()
layout = QtGui.QVBoxLayout(dialog)
button = QtGui.QPushButton("Generate", dialog)
progress = QtGui.QProgressBar(dialog)
progress.setRange(0, 0)
layout.addWidget(button)
layout.addWidget(progress)
button.clicked.connect(obj.fetch)
dialog.show()
app.exec_()
app.deleteLater() # avoids some QThread messages in the shell on exit
# cancel all running tasks avoid QThread/QTimer error messages
# on exit
Request.shutdown()
When exiting the application you'll want to make sure you cancel all of the tasks or the application will hang until every scheduled task has completed
Based on the Worker objects methods mentioned in other answers, I decided to see if I could expand on the solution to invoke more threads - in this case the optimal number the machine can run and spin up multiple workers with indeterminate completion times.
To do this I still need to subclass QThread - but only to assign a thread number and to 'reimplement' the signals 'finished' and 'started' to include their thread number.
I've focused quite a bit on the signals between the main gui, the threads, and the workers.
Similarly, others answers have been a pains to point out not parenting the QThread but I don't think this is a real concern. However, my code also is careful to destroy the QThread objects.
However, I wasn't able to parent the worker objects so it seems desirable to send them the deleteLater() signal, either when the thread function is finished or the GUI is destroyed. I've had my own code hang for not doing this.
Another enhancement I felt was necessary was was reimplement the closeEvent of the GUI (QWidget) such that the threads would be instructed to quit and then the GUI would wait until all the threads were finished. When I played with some of the other answers to this question, I got QThread destroyed errors.
Perhaps it will be useful to others. I certainly found it a useful exercise. Perhaps others will know a better way for a thread to announce it identity.
#!/usr/bin/env python3
#coding:utf-8
# Author: --<>
# Purpose: To demonstrate creation of multiple threads and identify the receipt of thread results
# Created: 19/12/15
import sys
from PyQt4.QtCore import QThread, pyqtSlot, pyqtSignal
from PyQt4.QtGui import QApplication, QLabel, QWidget, QGridLayout
import sys
import worker
class Thread(QThread):
#make new signals to be able to return an id for the thread
startedx = pyqtSignal(int)
finishedx = pyqtSignal(int)
def __init__(self,i,parent=None):
super().__init__(parent)
self.idd = i
self.started.connect(self.starttt)
self.finished.connect(self.finisheddd)
#pyqtSlot()
def starttt(self):
print('started signal from thread emitted')
self.startedx.emit(self.idd)
#pyqtSlot()
def finisheddd(self):
print('finished signal from thread emitted')
self.finishedx.emit(self.idd)
class Form(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.worker={}
self.threadx={}
self.i=0
i=0
#Establish the maximum number of threads the machine can optimally handle
#Generally relates to the number of processors
self.threadtest = QThread(self)
self.idealthreadcount = self.threadtest.idealThreadCount()
print("This machine can handle {} threads optimally".format(self.idealthreadcount))
while i <self.idealthreadcount:
self.setupThread(i)
i+=1
i=0
while i<self.idealthreadcount:
self.startThread(i)
i+=1
print("Main Gui running in thread {}.".format(self.thread()))
def setupThread(self,i):
self.worker[i]= worker.Worker(i) # no parent!
#print("Worker object runningt in thread {} prior to movetothread".format(self.worker[i].thread()) )
self.threadx[i] = Thread(i,parent=self) # if parent isn't specified then need to be careful to destroy thread
self.threadx[i].setObjectName("python thread{}"+str(i))
#print("Thread object runningt in thread {} prior to movetothread".format(self.threadx[i].thread()) )
self.threadx[i].startedx.connect(self.threadStarted)
self.threadx[i].finishedx.connect(self.threadFinished)
self.worker[i].finished.connect(self.workerFinished)
self.worker[i].intReady.connect(self.workerResultReady)
#The next line is optional, you may want to start the threads again without having to create all the code again.
self.worker[i].finished.connect(self.threadx[i].quit)
self.threadx[i].started.connect(self.worker[i].procCounter)
self.destroyed.connect(self.threadx[i].deleteLater)
self.destroyed.connect(self.worker[i].deleteLater)
#This is the key code that actually get the worker code onto another processor or thread.
self.worker[i].moveToThread(self.threadx[i])
def startThread(self,i):
self.threadx[i].start()
#pyqtSlot(int)
def threadStarted(self,i):
print('Thread {} started'.format(i))
print("Thread priority is {}".format(self.threadx[i].priority()))
#pyqtSlot(int)
def threadFinished(self,i):
print('Thread {} finished'.format(i))
#pyqtSlot(int)
def threadTerminated(self,i):
print("Thread {} terminated".format(i))
#pyqtSlot(int,int)
def workerResultReady(self,j,i):
print('Worker {} result returned'.format(i))
if i ==0:
self.label1.setText("{}".format(j))
if i ==1:
self.label2.setText("{}".format(j))
if i ==2:
self.label3.setText("{}".format(j))
if i ==3:
self.label4.setText("{}".format(j))
#print('Thread {} has started'.format(self.threadx[i].currentThreadId()))
#pyqtSlot(int)
def workerFinished(self,i):
print('Worker {} finished'.format(i))
def initUI(self):
self.label1 = QLabel("0")
self.label2= QLabel("0")
self.label3= QLabel("0")
self.label4 = QLabel("0")
grid = QGridLayout(self)
self.setLayout(grid)
grid.addWidget(self.label1,0,0)
grid.addWidget(self.label2,0,1)
grid.addWidget(self.label3,0,2)
grid.addWidget(self.label4,0,3) #Layout parents the self.labels
self.move(300, 150)
self.setGeometry(0,0,300,300)
#self.size(300,300)
self.setWindowTitle('thread test')
self.show()
def closeEvent(self, event):
print('Closing')
#this tells the threads to stop running
i=0
while i <self.idealthreadcount:
self.threadx[i].quit()
i+=1
#this ensures window cannot be closed until the threads have finished.
i=0
while i <self.idealthreadcount:
self.threadx[i].wait()
i+=1
event.accept()
if __name__=='__main__':
app = QApplication(sys.argv)
form = Form()
sys.exit(app.exec_())
And the worker code below
#!/usr/bin/env python3
#coding:utf-8
# Author: --<>
# Purpose: Stack Overflow
# Created: 19/12/15
import sys
import unittest
from PyQt4.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time
import random
class Worker(QObject):
finished = pyqtSignal(int)
intReady = pyqtSignal(int,int)
def __init__(self, i=0):
'''__init__ is called while the worker is still in the Gui thread. Do not put slow or CPU intensive code in the __init__ method'''
super().__init__()
self.idd = i
#pyqtSlot()
def procCounter(self): # This slot takes no params
for j in range(1, 10):
random_time = random.weibullvariate(1,2)
time.sleep(random_time)
self.intReady.emit(j,self.idd)
print('Worker {0} in thread {1}'.format(self.idd, self.thread().idd))
self.finished.emit(self.idd)
if __name__=='__main__':
unittest.main()
PySide2 Solution:
Unlike in PyQt5, in PySide2 the QThread.started signal is received/handled on the original thread, not the worker thread! Luckily it still receives all other signals on the worker thread.
In order to match PyQt5's behavior, you have to create the started signal yourself.
Here is an easy solution:
# Use this class instead of QThread
class QThread2(QThread):
# Use this signal instead of "started"
started2 = Signal()
def __init__(self):
QThread.__init__(self)
self.started.connect(self.onStarted)
def onStarted(self):
self.started2.emit()
I have:
from PySide.QtCore import Signal, QObject
from multiprocessing import Pool
def some_computation():
pass
# ..some computations
return 'result'
class MyClass(QObject):
my_signal = Signal()
def __init__(self):
self.mylistwidget = # ... QListWidget from somewhere
# bind the signal to my slot
self.my_signal.connect(self.on_my_signal)
# this is called after computation thread is finished
def my_callback_fct(result):
# ..bla bla
self.my_signal.emit()
# this is the function I call
def do_some_async_computation(self)
pool = Pool(processes=2)
pool.apply_async(target=some_computation, callback=my_callback_fct)
# this is the slot
def on_my_signal(self):
self.mylistwidget.clear()
I read around stackoverflow that in order to change the gui from a secondary execution thread one must use slot-signal mechanism, which is what I did in MyClass, although when I call do_some_async_computation I would expect the pool to initiate a secondary thread for some_computation function ..which happens, after the computation is finished the my_callback_fct is executed accordingly, which emits a my_signal signal that is connected to the on_my_signal slot, which is executed as expected, but when altering the self.mylistwidget it gives a Runtime Error / QWidget runtime error redundant repaint detected
I haven't observed your actual error, but in a similar scenario we use a QueuedConnection to ensure the signal is passed correctly from one thread to the other. This is done automagically for some people if the objects in question belong to different threads (QObject have a notion of the QThread that owns them). But in your case, all is done on one object, so Qt can't know. Do
from PyQt5.QtCore import Qt
...
self.my_signal.connect(self.on_my_signal, Qt.QueuedConnection)
I solved this by using a QtCore.QThread instead of multiprocessing.Pool. I was thinking about the mechanism you talked about #deets and I said to myself that it should be in in the same context in order for Qt.QueuedConnection to work, so that's why I wanted to go with QThread.
class MyComputationalThread(PySide.QtCore.QThread):
data_available = PySide.QtCore.Signal(str)
def run(self):
result = # ... do computations
serialized_result = json.dumps(result) # actually I use JSONEncoder here
self.data_available.emit(serialized_result)
... and in my MyClass:
class MyClass(QObject):
# ...
mythread = MyComputationalThread()
# ...
def __init__(self):
# ...
self.mythread.connect(do_something_with_serialized_data, PySide.QtCore.Qt.QueuedConnection)
# ...
It works like a charm.
I'm having troubles using PyQt4 slots/signals.
I'm using PyLIRC and I'm listening for button presses on a remote. This part I have gotten to work outside of Qt. My problem comes when emitting the signal from the button listening thread and attempting to call a slot in the main thread.
My button listener is a QObject initialized like so:
buttonPressed = pyqtSignal(int)
def __init__(self):
super(ButtonEvent, self).__init__()
self.buttonPressed.connect(self.onButtonPressed)
def run(self):
print 'running'
while(self._isListening):
s = pylirc.nextcode()
if (s):
print 'emitting'
self.buttonPressed.emit(int(s[0]))
The onButtonPressed slot is internal to the button listener for testing purposes.
To move the button listener to another thread to do the work, I use the following:
event = ButtonEvent()
eventThread = QThread()
event.moveToThread(eventThread)
eventThread.started.connect(event.run)
Then in the main thread, I have my VideoTableController class that contains the slot in the main thread that doesn't get called. Inside of __init__ I have this:
class VideoTableController(QObject):
def __init__(self, buttonEvent):
buttonEvent.buttonPressed.connect(self.onButtonPressed)
Where onButtonPressed in this case is:
#pyqtSlot(int)
def onButtonPressed(self, bid):
print 'handling button press'
if bid not in listenButtons: return
{ ButtonEnum.KEY_LEFT : self.handleBack,
#...
So when I start the event thread, it starts listening properly. When I press a button on the remote, the onButtonPressed slot internal to the ButtonEvent class is properly called, but the slot within VideoTableController, which resides in the main thread, is not called. I started my listening thread after connecting the slot to the signal, and I tested doing it the other way around, but to no avail.
I have looked around, but I haven't been able to find anything. I changed over to using QObject after reading You're doing it wrong. Any help with this is greatly appreciated. Let me know if you need anything else.
EDIT: Thanks for the responses! Here is a big chunk of code for you guys:
ButtonEvent (This class uses singleton pattern, excuse the poor coding because I'm somewhat new to this territory of Python also):
import pylirc
from PyQt4.QtCore import QObject, pyqtSignal, QThread, pyqtSlot
from PyQt4 import QtCore
class ButtonEvent(QObject):
"""
A class used for firing button events
"""
_instance = None
_blocking = 0
_isListening = False
buttonPressed = pyqtSignal(int)
def __new__(cls, configFileName="~/.lircrc", blocking=0, *args, **kwargs):
if not cls._instance:
cls._instance = super(ButtonEvent, cls).__new__(cls, args, kwargs)
cls._blocking = blocking
if not pylirc.init("irexec", configFileName, blocking):
raise RuntimeError("Problem initilizing PyLIRC")
cls._isListening = True
return cls._instance
def __init__(self):
"""
Creates an instance of the ButtonEvent class
"""
super(ButtonEvent, self).__init__()
self.buttonPressed.connect(self.button)
### init
def run(self):
print 'running'
while(self._isListening):
s = pylirc.nextcode()
if (s):
print 'emitting'
self.buttonPressed.emit(int(s[0]))
def stopListening(self):
print 'stopping'
self._isListening = False
#pyqtSlot(int)
def button(self, bid):
print 'Got ' + str(bid)
def setupAndConnectButtonEvent(configFileName="~/.lircrc", blocking=0):
"""
Initializes the ButtonEvent and puts it on a QThread.
Returns the QThread it is running on.
Does not start the thread
"""
event = ButtonEvent().__new__(ButtonEvent, configFileName, blocking)
eventThread = QThread()
event.moveToThread(eventThread)
eventThread.started.connect(event.run)
return eventThread
Here is the VideoTableController:
from ControllerBase import ControllerBase
from ButtonEnum import ButtonEnum
from ButtonEvent import ButtonEvent
from PyQt4.QtCore import pyqtSlot
from PyQt4 import QtCore
class VideoTableController(ControllerBase):
listenButtons = [ ButtonEnum.KEY_LEFT,
ButtonEnum.KEY_UP,
ButtonEnum.KEY_OK,
ButtonEnum.KEY_RIGHT,
ButtonEnum.KEY_DOWN,
ButtonEnum.KEY_BACK ]
def __init__(self, model, view, parent=None):
super(VideoTableController, self).__init__(model, view, parent)
self._currentRow = 0
buttonEvent = ButtonEvent()
buttonEvent.buttonPressed.connect(self.onButtonPressed)
self.selectRow(self._currentRow)
#pyqtSlot(int)
def onButtonPressed(self, bid):
print 'handling button press'
if bid not in listenButtons: return
{ ButtonEnum.KEY_LEFT : self.handleBack,
ButtonEnum.KEY_UP : self.handleUp,
ButtonEnum.KEY_OK : self.handleOk,
ButtonEnum.KEY_RIGHT : self.handleRight,
ButtonEnum.KEY_DOWN : self.handleDown,
ButtonEnum.KEY_BACK : self.handleBack,
}.get(bid, None)()
And here is my startup script:
import sys
from PyQt4 import QtCore, QtGui
from ui_main import Ui_MainWindow
from VideoTableModel import VideoTableModel
from VideoTableController import VideoTableController
from ButtonEvent import *
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.buttonEvent = ButtonEvent()
self.bEventThread = setupAndConnectButtonEvent()
model = VideoTableModel("/home/user/Videos")
self.ui.videoView.setModel(model)
controller = VideoTableController(model, self.ui.videoView)
self.bEventThread.start()
def closeEvent(self, event):
self.buttonEvent.stopListening()
self.bEventThread.quit()
event.accept()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
buttonEvent = ButtonEvent()
myapp = Main()
myapp.show()
sys.exit(app.exec_())
It turns out I was just making a foolish Python mistake. The signal was being emitted correctly, and the event loop was running properly in all threads. My problem was that in my Main.__init__ function I made a VideoTableController object, but I did not keep a copy in Main, so my controller did not persist, meaning the slot also left. When changing it to
self.controller = VideoTableController(model, self.ui.videoView)
Everything stayed around and the slots were called properly.
Moral of the story: it's not always a misuse of the library, it may be a misuse of the language.
It seems that the quickest workaround would be change your ButtonEvent code here:
...
def run(self):
print 'running'
while(self._isListening):
s = pylirc.nextcode()
if (s):
print 'emitting'
self.buttonPressed.emit(int(s[0]))
...
to this:
#pyqtSlot()
def run(self):
print 'running'
while(self._isListening):
s = pylirc.nextcode()
if (s):
print 'emitting'
self.buttonPressed.emit(int(s[0]))
The short explanation to this issue is that PyQt uses a proxy internally, and this way you can make sure to avoid that. After all, your method is supposed to be a slot based on the connect statement.
Right... Now, I would encourage you to give some consideration for your current software design though. It seems that you are using a class in a dedicated thread for handling Qt button events. It may be good idea, I am not sure, but I have not seen this before at least.
I think you could get rid of that class altogether in the future with a better approach where you connect from the push button signals directly to your handler slot. That would not be the run "slot" in your dedicated thread, however, but the cannonical handler.
It is not a good design practice to introduce more complexity, especially in multi-threaded applications, than needed. Hope this helps.
I haven't actually tested this (because I don't have access to your compiled UI file), but I'm fairly certain I'm right.
Your run method of your ButtonEvent (which is supposed to be running in a thread) is likely running in the mainthread (you can test this by importing the python threading module and adding the line print threading.current_thread().name. To solve this, decorate your run method with #pyqtSlot()
If that doesn't solve it, add the above print statement to various places until you find something running in the main thread that shouldn't be. The lined SO answer below will likely contain the answer to fix it.
For more details, see this answer: https://stackoverflow.com/a/20818401/1994235
First off, I'm very new to Python and Pyside. In order to do a bit of self-improvement, I'm trying to get a QTimer to execute every second in a child thread of my PySide program (at the moment I just want it to print "hi!" to a terminal every second without freezing the main window).
I tried converting the example I found on the Qt Wiki from C++ to Python/PySide, but since I don't really know C++ I assume I converted it incorrectly and that's why it's not working properly.
At the moment, the doWork() function only seems to execute once and then never again. What am I doing wrong? Is there a better way to execute a function every second in PySide without freezing the main window?
Here's the code (I have removed some main window code to increase clarity):
from PySide import QtGui
from PySide import QtCore
from client_gui import Ui_MainWindow
statsThread = QtCore.QThread()
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
#setup GUI
self.setupUi(self)
#start thread to update GUI
self.statsThread = updateStatsThread()
self.statsThread.start(QtCore.QThread.TimeCriticalPriority)
class updateGuiWithStats(QtCore.QObject):
def Worker(self):
timer = QtCore.QTimer()
timer.timeout.connect(self.doWork())
timer.start(1000)
def doWork(self):
print "hi!"
class updateStatsThread (QtCore.QThread):
def run(self):
updater = updateGuiWithStats()
updater.Worker()
self.exec_()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
frame = MainWindow()
frame.show()
sys.exit(app.exec_())
#Masci already pointed out the fix you needed for your timer.timeout.connect, but I see more issues than just that.
No need to create a global QThread that is never used:
statsThread = QtCore.QThread()
Your QTimer is being garbage collected right away because its created without a parent, and you aren't saving it within your class. This is why even after you fix your timer connection, it will still not work... Try:
class UpdateGuiWithStats(QtCore.QObject):
def startWorker(self):
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.doWork)
self.timer.start(1000)
Also, use UpperCase for the first letter of classes, and camelCase for methods. You are doing a mixture of both ways.
A couple of notes based on that link you provided, your example, and other comments on here... You can use just a QTimer as a solution if your doWork() is very light and will not block your main event loop with a bunch of data crunching, sleeping, etc. If it does, then doWork() will need to be moved to a QThread, as your example is doing. But at that point it is somewhat unnecessary to use an event loop, and a QTimer in a separate class that calls its own work. This all could be consolidated into a single class, something like:
class UpdateStatsThread(QtCore.QThread):
def __init__(self, parent=None):
super(UpdateStatsThread, self).__init__(parent)
self._running = False
def run(self):
self._running = True
while self._running:
self.doWork()
self.msleep(1000)
def stop(self, wait=False):
self._running = False
if wait:
self.wait()
def doWork(self):
print "hi!"
in updateGuiWithStats class, Worker method:
timer.timeout.connect(self.doWork())
should be
timer.timeout.connect(self.doWork)
You are connecting timeout signal to None (the return value of doWork() method), and I think this is why it is executed only once: doWork is called during the connection and nomore. When you make connections, remember to connect the function name (in Pythonics words, the callable object) and not the function call.
By the way, even if the above solved your problem, you should avoid using threads since QTimer already does by its own you need. In the docs you linked, the first answer to the When shouldn’t I use threads? question is: Timers.
I have a program which interfaces with a radio I am using via a gui I wrote in PyQt. Obviously one of the main functions of the radio is to transmit data, but to do this continuously, I have to loop the writes, which causes the gui to hang. Since I have never dealt with threading, I tried to get rid of these hangs using QCoreApplication.processEvents(). The radio needs to sleep between transmissions, though, so the gui still hangs based on how long these sleeps last.
Is there a simple way to fix this using QThread? I have looked for tutorials on how to implement multithreading with PyQt, but most of them deal with setting up servers and are much more advanced than I need them to be. I honestly don't even really need my thread to update anything while it is running, I just need to start it, have it transmit in the background, and stop it.
I created a little example that shows 3 different and simple ways of dealing with threads. I hope it will help you find the right approach to your problem.
import sys
import time
from PyQt5.QtCore import (QCoreApplication, QObject, QRunnable, QThread,
QThreadPool, pyqtSignal)
# Subclassing QThread
# http://qt-project.org/doc/latest/qthread.html
class AThread(QThread):
def run(self):
count = 0
while count < 5:
time.sleep(1)
print("A Increasing")
count += 1
# Subclassing QObject and using moveToThread
# http://blog.qt.digia.com/blog/2007/07/05/qthreads-no-longer-abstract
class SomeObject(QObject):
finished = pyqtSignal()
def long_running(self):
count = 0
while count < 5:
time.sleep(1)
print("B Increasing")
count += 1
self.finished.emit()
# Using a QRunnable
# http://qt-project.org/doc/latest/qthreadpool.html
# Note that a QRunnable isn't a subclass of QObject and therefore does
# not provide signals and slots.
class Runnable(QRunnable):
def run(self):
count = 0
app = QCoreApplication.instance()
while count < 5:
print("C Increasing")
time.sleep(1)
count += 1
app.quit()
def using_q_thread():
app = QCoreApplication([])
thread = AThread()
thread.finished.connect(app.exit)
thread.start()
sys.exit(app.exec_())
def using_move_to_thread():
app = QCoreApplication([])
objThread = QThread()
obj = SomeObject()
obj.moveToThread(objThread)
obj.finished.connect(objThread.quit)
objThread.started.connect(obj.long_running)
objThread.finished.connect(app.exit)
objThread.start()
sys.exit(app.exec_())
def using_q_runnable():
app = QCoreApplication([])
runnable = Runnable()
QThreadPool.globalInstance().start(runnable)
sys.exit(app.exec_())
if __name__ == "__main__":
#using_q_thread()
#using_move_to_thread()
using_q_runnable()
Take this answer updated for PyQt5, python 3.4
Use this as a pattern to start a worker that does not take data and return data as they are available to the form.
1 - Worker class is made smaller and put in its own file worker.py for easy memorization and independent software reuse.
2 - The main.py file is the file that defines the GUI Form class
3 - The thread object is not subclassed.
4 - Both thread object and the worker object belong to the Form object
5 - Steps of the procedure are within the comments.
# worker.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int)
#pyqtSlot()
def procCounter(self): # A slot takes no params
for i in range(1, 100):
time.sleep(1)
self.intReady.emit(i)
self.finished.emit()
And the main file is:
# main.py
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout
import sys
import worker
class Form(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("0")
# 1 - create Worker and Thread inside the Form
self.obj = worker.Worker() # no parent!
self.thread = QThread() # no parent!
# 2 - Connect Worker`s Signals to Form method slots to post data.
self.obj.intReady.connect(self.onIntReady)
# 3 - Move the Worker object to the Thread object
self.obj.moveToThread(self.thread)
# 4 - Connect Worker Signals to the Thread slots
self.obj.finished.connect(self.thread.quit)
# 5 - Connect Thread started signal to Worker operational slot method
self.thread.started.connect(self.obj.procCounter)
# * - Thread finished signal will close the app if you want!
#self.thread.finished.connect(app.exit)
# 6 - Start the thread
self.thread.start()
# 7 - Start the form
self.initUI()
def initUI(self):
grid = QGridLayout()
self.setLayout(grid)
grid.addWidget(self.label,0,0)
self.move(300, 150)
self.setWindowTitle('thread test')
self.show()
def onIntReady(self, i):
self.label.setText("{}".format(i))
#print(i)
app = QApplication(sys.argv)
form = Form()
sys.exit(app.exec_())
According to the Qt developers, subclassing QThread is incorrect (see http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/). But that article is really hard to understand (plus the title is a bit condescending). I found a better blog post that gives a more detailed explanation about why you should use one style of threading over another: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
Also, I would highly recommend this video from KDAB on signals and slots between threads.
In my opinion, you should probably never subclass thread with the intent to overload the run method. While that does work, you're basically circumventing how Qt wants you to work. Plus you'll miss out on things like events and proper thread safe signals and slots. Plus as you'll likely see in the above blog post, the "correct" way of threading forces you to write more testable code.
Here's a couple of examples of how to take advantage of QThreads in PyQt (I posted a separate answer below that properly uses QRunnable and incorporates signals/slots, that answer is better if you have a lot of async tasks that you need to load balance).
import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt
# very testable class (hint: you can use mock.Mock for the signals)
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
dataReady = QtCore.pyqtSignal(list, dict)
#QtCore.pyqtSlot()
def processA(self):
print "Worker.processA()"
self.finished.emit()
#QtCore.pyqtSlot(str, list, list)
def processB(self, foo, bar=None, baz=None):
print "Worker.processB()"
for thing in bar:
# lots of processing...
self.dataReady.emit(['dummy', 'data'], {'dummy': ['data']})
self.finished.emit()
class Thread(QtCore.QThread):
"""Need for PyQt4 <= 4.6 only"""
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
# this class is solely needed for these two methods, there
# appears to be a bug in PyQt 4.6 that requires you to
# explicitly call run and start from the subclass in order
# to get the thread to actually start an event loop
def start(self):
QtCore.QThread.start(self)
def run(self):
QtCore.QThread.run(self)
app = QtGui.QApplication(sys.argv)
thread = Thread() # no parent!
obj = Worker() # no parent!
obj.moveToThread(thread)
# if you want the thread to stop after the worker is done
# you can always call thread.start() again later
obj.finished.connect(thread.quit)
# one way to do it is to start processing as soon as the thread starts
# this is okay in some cases... but makes it harder to send data to
# the worker object from the main gui thread. As you can see I'm calling
# processA() which takes no arguments
thread.started.connect(obj.processA)
thread.start()
# another way to do it, which is a bit fancier, allows you to talk back and
# forth with the object in a thread safe way by communicating through signals
# and slots (now that the thread is running I can start calling methods on
# the worker object)
QtCore.QMetaObject.invokeMethod(obj, 'processB', Qt.QueuedConnection,
QtCore.Q_ARG(str, "Hello World!"),
QtCore.Q_ARG(list, ["args", 0, 1]),
QtCore.Q_ARG(list, []))
# that looks a bit scary, but its a totally ok thing to do in Qt,
# we're simply using the system that Signals and Slots are built on top of,
# the QMetaObject, to make it act like we safely emitted a signal for
# the worker thread to pick up when its event loop resumes (so if its doing
# a bunch of work you can call this method 10 times and it will just queue
# up the calls. Note: PyQt > 4.6 will not allow you to pass in a None
# instead of an empty list, it has stricter type checking
app.exec_()
# Without this you may get weird QThread messages in the shell on exit
app.deleteLater()
Very nice example from Matt, I fixed the typo and also pyqt4.8 is common now so I removed the dummy class as well and added an example for the dataReady signal
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import Qt
# very testable class (hint: you can use mock.Mock for the signals)
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
dataReady = QtCore.pyqtSignal(list, dict)
#QtCore.pyqtSlot()
def processA(self):
print "Worker.processA()"
self.finished.emit()
#QtCore.pyqtSlot(str, list, list)
def processB(self, foo, bar=None, baz=None):
print "Worker.processB()"
for thing in bar:
# lots of processing...
self.dataReady.emit(['dummy', 'data'], {'dummy': ['data']})
self.finished.emit()
def onDataReady(aList, aDict):
print 'onDataReady'
print repr(aList)
print repr(aDict)
app = QtGui.QApplication(sys.argv)
thread = QtCore.QThread() # no parent!
obj = Worker() # no parent!
obj.dataReady.connect(onDataReady)
obj.moveToThread(thread)
# if you want the thread to stop after the worker is done
# you can always call thread.start() again later
obj.finished.connect(thread.quit)
# one way to do it is to start processing as soon as the thread starts
# this is okay in some cases... but makes it harder to send data to
# the worker object from the main gui thread. As you can see I'm calling
# processA() which takes no arguments
thread.started.connect(obj.processA)
thread.finished.connect(app.exit)
thread.start()
# another way to do it, which is a bit fancier, allows you to talk back and
# forth with the object in a thread safe way by communicating through signals
# and slots (now that the thread is running I can start calling methods on
# the worker object)
QtCore.QMetaObject.invokeMethod(obj, 'processB', Qt.QueuedConnection,
QtCore.Q_ARG(str, "Hello World!"),
QtCore.Q_ARG(list, ["args", 0, 1]),
QtCore.Q_ARG(list, []))
# that looks a bit scary, but its a totally ok thing to do in Qt,
# we're simply using the system that Signals and Slots are built on top of,
# the QMetaObject, to make it act like we safely emitted a signal for
# the worker thread to pick up when its event loop resumes (so if its doing
# a bunch of work you can call this method 10 times and it will just queue
# up the calls. Note: PyQt > 4.6 will not allow you to pass in a None
# instead of an empty list, it has stricter type checking
app.exec_()
In PyQt there are a lot of options for getting asynchronous behavior. For things that need event processing (ie. QtNetwork, etc) you should use the QThread example I provided in my other answer on this thread. But for the vast majority of your threading needs, I think this solution is far superior than the other methods.
The advantage of this is that the QThreadPool schedules your QRunnable instances as tasks. This is similar to the task pattern used in Intel's TBB. It's not quite as elegant as I like but it does pull off excellent asynchronous behavior.
This allows you to utilize most of the threading power of Qt in Python via QRunnable and still take advantage of signals and slots. I use this same code in several applications, some that make hundreds of asynchronous REST calls, some that open files or list directories, and the best part is using this method, Qt task balances the system resources for me.
import time
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt
def async(method, args, uid, readycb, errorcb=None):
"""
Asynchronously runs a task
:param func method: the method to run in a thread
:param object uid: a unique identifier for this task (used for verification)
:param slot updatecb: the callback when data is receieved cb(uid, data)
:param slot errorcb: the callback when there is an error cb(uid, errmsg)
The uid option is useful when the calling code makes multiple async calls
and the callbacks need some context about what was sent to the async method.
For example, if you use this method to thread a long running database call
and the user decides they want to cancel it and start a different one, the
first one may complete before you have a chance to cancel the task. In that
case, the "readycb" will be called with the cancelled task's data. The uid
can be used to differentiate those two calls (ie. using the sql query).
:returns: Request instance
"""
request = Request(method, args, uid, readycb, errorcb)
QtCore.QThreadPool.globalInstance().start(request)
return request
class Request(QtCore.QRunnable):
"""
A Qt object that represents an asynchronous task
:param func method: the method to call
:param list args: list of arguments to pass to method
:param object uid: a unique identifier (used for verification)
:param slot readycb: the callback used when data is receieved
:param slot errorcb: the callback used when there is an error
The uid param is sent to your error and update callbacks as the
first argument. It's there to verify the data you're returning
After created it should be used by invoking:
.. code-block:: python
task = Request(...)
QtCore.QThreadPool.globalInstance().start(task)
"""
INSTANCES = []
FINISHED = []
def __init__(self, method, args, uid, readycb, errorcb=None):
super(Request, self).__init__()
self.setAutoDelete(True)
self.cancelled = False
self.method = method
self.args = args
self.uid = uid
self.dataReady = readycb
self.dataError = errorcb
Request.INSTANCES.append(self)
# release all of the finished tasks
Request.FINISHED = []
def run(self):
"""
Method automatically called by Qt when the runnable is ready to run.
This will run in a separate thread.
"""
# this allows us to "cancel" queued tasks if needed, should be done
# on shutdown to prevent the app from hanging
if self.cancelled:
self.cleanup()
return
# runs in a separate thread, for proper async signal/slot behavior
# the object that emits the signals must be created in this thread.
# Its not possible to run grabber.moveToThread(QThread.currentThread())
# so to get this QObject to properly exhibit asynchronous
# signal and slot behavior it needs to live in the thread that
# we're running in, creating the object from within this thread
# is an easy way to do that.
grabber = Requester()
grabber.Loaded.connect(self.dataReady, Qt.QueuedConnection)
if self.dataError is not None:
grabber.Error.connect(self.dataError, Qt.QueuedConnection)
try:
result = self.method(*self.args)
if self.cancelled:
# cleanup happens in 'finally' statement
return
grabber.Loaded.emit(self.uid, result)
except Exception as error:
if self.cancelled:
# cleanup happens in 'finally' statement
return
grabber.Error.emit(self.uid, unicode(error))
finally:
# this will run even if one of the above return statements
# is executed inside of the try/except statement see:
# https://docs.python.org/2.7/tutorial/errors.html#defining-clean-up-actions
self.cleanup(grabber)
def cleanup(self, grabber=None):
# remove references to any object or method for proper ref counting
self.method = None
self.args = None
self.uid = None
self.dataReady = None
self.dataError = None
if grabber is not None:
grabber.deleteLater()
# make sure this python obj gets cleaned up
self.remove()
def remove(self):
try:
Request.INSTANCES.remove(self)
# when the next request is created, it will clean this one up
# this will help us avoid this object being cleaned up
# when it's still being used
Request.FINISHED.append(self)
except ValueError:
# there might be a race condition on shutdown, when shutdown()
# is called while the thread is still running and the instance
# has already been removed from the list
return
#staticmethod
def shutdown():
for inst in Request.INSTANCES:
inst.cancelled = True
Request.INSTANCES = []
Request.FINISHED = []
class Requester(QtCore.QObject):
"""
A simple object designed to be used in a separate thread to allow
for asynchronous data fetching
"""
#
# Signals
#
Error = QtCore.pyqtSignal(object, unicode)
"""
Emitted if the fetch fails for any reason
:param unicode uid: an id to identify this request
:param unicode error: the error message
"""
Loaded = QtCore.pyqtSignal(object, object)
"""
Emitted whenever data comes back successfully
:param unicode uid: an id to identify this request
:param list data: the json list returned from the GET
"""
NetworkConnectionError = QtCore.pyqtSignal(unicode)
"""
Emitted when the task fails due to a network connection error
:param unicode message: network connection error message
"""
def __init__(self, parent=None):
super(Requester, self).__init__(parent)
class ExampleObject(QtCore.QObject):
def __init__(self, parent=None):
super(ExampleObject, self).__init__(parent)
self.uid = 0
self.request = None
def ready_callback(self, uid, result):
if uid != self.uid:
return
print "Data ready from %s: %s" % (uid, result)
def error_callback(self, uid, error):
if uid != self.uid:
return
print "Data error from %s: %s" % (uid, error)
def fetch(self):
if self.request is not None:
# cancel any pending requests
self.request.cancelled = True
self.request = None
self.uid += 1
self.request = async(slow_method, ["arg1", "arg2"], self.uid,
self.ready_callback,
self.error_callback)
def slow_method(arg1, arg2):
print "Starting slow method"
time.sleep(1)
return arg1 + arg2
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
obj = ExampleObject()
dialog = QtGui.QDialog()
layout = QtGui.QVBoxLayout(dialog)
button = QtGui.QPushButton("Generate", dialog)
progress = QtGui.QProgressBar(dialog)
progress.setRange(0, 0)
layout.addWidget(button)
layout.addWidget(progress)
button.clicked.connect(obj.fetch)
dialog.show()
app.exec_()
app.deleteLater() # avoids some QThread messages in the shell on exit
# cancel all running tasks avoid QThread/QTimer error messages
# on exit
Request.shutdown()
When exiting the application you'll want to make sure you cancel all of the tasks or the application will hang until every scheduled task has completed
Based on the Worker objects methods mentioned in other answers, I decided to see if I could expand on the solution to invoke more threads - in this case the optimal number the machine can run and spin up multiple workers with indeterminate completion times.
To do this I still need to subclass QThread - but only to assign a thread number and to 'reimplement' the signals 'finished' and 'started' to include their thread number.
I've focused quite a bit on the signals between the main gui, the threads, and the workers.
Similarly, others answers have been a pains to point out not parenting the QThread but I don't think this is a real concern. However, my code also is careful to destroy the QThread objects.
However, I wasn't able to parent the worker objects so it seems desirable to send them the deleteLater() signal, either when the thread function is finished or the GUI is destroyed. I've had my own code hang for not doing this.
Another enhancement I felt was necessary was was reimplement the closeEvent of the GUI (QWidget) such that the threads would be instructed to quit and then the GUI would wait until all the threads were finished. When I played with some of the other answers to this question, I got QThread destroyed errors.
Perhaps it will be useful to others. I certainly found it a useful exercise. Perhaps others will know a better way for a thread to announce it identity.
#!/usr/bin/env python3
#coding:utf-8
# Author: --<>
# Purpose: To demonstrate creation of multiple threads and identify the receipt of thread results
# Created: 19/12/15
import sys
from PyQt4.QtCore import QThread, pyqtSlot, pyqtSignal
from PyQt4.QtGui import QApplication, QLabel, QWidget, QGridLayout
import sys
import worker
class Thread(QThread):
#make new signals to be able to return an id for the thread
startedx = pyqtSignal(int)
finishedx = pyqtSignal(int)
def __init__(self,i,parent=None):
super().__init__(parent)
self.idd = i
self.started.connect(self.starttt)
self.finished.connect(self.finisheddd)
#pyqtSlot()
def starttt(self):
print('started signal from thread emitted')
self.startedx.emit(self.idd)
#pyqtSlot()
def finisheddd(self):
print('finished signal from thread emitted')
self.finishedx.emit(self.idd)
class Form(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.worker={}
self.threadx={}
self.i=0
i=0
#Establish the maximum number of threads the machine can optimally handle
#Generally relates to the number of processors
self.threadtest = QThread(self)
self.idealthreadcount = self.threadtest.idealThreadCount()
print("This machine can handle {} threads optimally".format(self.idealthreadcount))
while i <self.idealthreadcount:
self.setupThread(i)
i+=1
i=0
while i<self.idealthreadcount:
self.startThread(i)
i+=1
print("Main Gui running in thread {}.".format(self.thread()))
def setupThread(self,i):
self.worker[i]= worker.Worker(i) # no parent!
#print("Worker object runningt in thread {} prior to movetothread".format(self.worker[i].thread()) )
self.threadx[i] = Thread(i,parent=self) # if parent isn't specified then need to be careful to destroy thread
self.threadx[i].setObjectName("python thread{}"+str(i))
#print("Thread object runningt in thread {} prior to movetothread".format(self.threadx[i].thread()) )
self.threadx[i].startedx.connect(self.threadStarted)
self.threadx[i].finishedx.connect(self.threadFinished)
self.worker[i].finished.connect(self.workerFinished)
self.worker[i].intReady.connect(self.workerResultReady)
#The next line is optional, you may want to start the threads again without having to create all the code again.
self.worker[i].finished.connect(self.threadx[i].quit)
self.threadx[i].started.connect(self.worker[i].procCounter)
self.destroyed.connect(self.threadx[i].deleteLater)
self.destroyed.connect(self.worker[i].deleteLater)
#This is the key code that actually get the worker code onto another processor or thread.
self.worker[i].moveToThread(self.threadx[i])
def startThread(self,i):
self.threadx[i].start()
#pyqtSlot(int)
def threadStarted(self,i):
print('Thread {} started'.format(i))
print("Thread priority is {}".format(self.threadx[i].priority()))
#pyqtSlot(int)
def threadFinished(self,i):
print('Thread {} finished'.format(i))
#pyqtSlot(int)
def threadTerminated(self,i):
print("Thread {} terminated".format(i))
#pyqtSlot(int,int)
def workerResultReady(self,j,i):
print('Worker {} result returned'.format(i))
if i ==0:
self.label1.setText("{}".format(j))
if i ==1:
self.label2.setText("{}".format(j))
if i ==2:
self.label3.setText("{}".format(j))
if i ==3:
self.label4.setText("{}".format(j))
#print('Thread {} has started'.format(self.threadx[i].currentThreadId()))
#pyqtSlot(int)
def workerFinished(self,i):
print('Worker {} finished'.format(i))
def initUI(self):
self.label1 = QLabel("0")
self.label2= QLabel("0")
self.label3= QLabel("0")
self.label4 = QLabel("0")
grid = QGridLayout(self)
self.setLayout(grid)
grid.addWidget(self.label1,0,0)
grid.addWidget(self.label2,0,1)
grid.addWidget(self.label3,0,2)
grid.addWidget(self.label4,0,3) #Layout parents the self.labels
self.move(300, 150)
self.setGeometry(0,0,300,300)
#self.size(300,300)
self.setWindowTitle('thread test')
self.show()
def closeEvent(self, event):
print('Closing')
#this tells the threads to stop running
i=0
while i <self.idealthreadcount:
self.threadx[i].quit()
i+=1
#this ensures window cannot be closed until the threads have finished.
i=0
while i <self.idealthreadcount:
self.threadx[i].wait()
i+=1
event.accept()
if __name__=='__main__':
app = QApplication(sys.argv)
form = Form()
sys.exit(app.exec_())
And the worker code below
#!/usr/bin/env python3
#coding:utf-8
# Author: --<>
# Purpose: Stack Overflow
# Created: 19/12/15
import sys
import unittest
from PyQt4.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time
import random
class Worker(QObject):
finished = pyqtSignal(int)
intReady = pyqtSignal(int,int)
def __init__(self, i=0):
'''__init__ is called while the worker is still in the Gui thread. Do not put slow or CPU intensive code in the __init__ method'''
super().__init__()
self.idd = i
#pyqtSlot()
def procCounter(self): # This slot takes no params
for j in range(1, 10):
random_time = random.weibullvariate(1,2)
time.sleep(random_time)
self.intReady.emit(j,self.idd)
print('Worker {0} in thread {1}'.format(self.idd, self.thread().idd))
self.finished.emit(self.idd)
if __name__=='__main__':
unittest.main()
PySide2 Solution:
Unlike in PyQt5, in PySide2 the QThread.started signal is received/handled on the original thread, not the worker thread! Luckily it still receives all other signals on the worker thread.
In order to match PyQt5's behavior, you have to create the started signal yourself.
Here is an easy solution:
# Use this class instead of QThread
class QThread2(QThread):
# Use this signal instead of "started"
started2 = Signal()
def __init__(self):
QThread.__init__(self)
self.started.connect(self.onStarted)
def onStarted(self):
self.started2.emit()