Passing thread to threaded object - python

Quick question on the use of QThread in PyQt4 and Python 2.7. I am creating a process inherited from QObject, and assigning this to a Qthread I have created in a separate class (also inherited from QObject).
Is it safe to pass the QThread object to the process object, so that I can call thread.msleep(mseconds) from within the process itself?
I want to be able to make the thread wait or sleep, but I have read that time.sleep(seconds) is dodgy when used with PyQt multi-threading.
I did try to send a signal from the process object to a slot in the main thread (attached to thread.msleep(mseconds) for that process object), but I found that this failed to work; the process object continued executing until complete, with the slot only being executed after this time. Even after adjusting priorities, this continued to happen. This is unacceptable since I want the process loop to run continuously.
Any other recommendations?

I eventually managed to alter my code to achieve the functionality that I required in my question: namely the ability to make a thread wait or sleep for a specified amount of time.
Firstly, my research seems to show that one of the main reasons subclassing QThread became ill-advised in Qt was that a thread should not be able to manage itself. Though there is no official documentation on my question, I can only surmise that passing the thread object to the process object running on it would also be ill-advised, because the thread would again be able to control itself directly.
The solution I have found is to dispense with msleep() altogether. Qt documentation on QThread recommends that sleep() and wait() functions are avoided because they do not fit well with the event driven nature of Qt. They recommend that QTimer() is used to call a function via a signal after it times out, in place of msleep(). By default QTimer() is used to send a repeating signal every time interval, but can also send a signal once using QTimer.singleShot(). It is also stated in the documentation that it is safe to call QSleep() from within a thread.
I only use a repeating QTimer to call a single slot foo() multiple times, but to add a delay within foo(), QTimer.singleShot() could be used to call a second function moo() after a set number of milliseconds.
EDIT: I have decided to include my threading code, which subclasses QObject and QThread to perform a task on a thread in a continual loop every given time interval. It is, as far as I can tell, fully functional, though could do with a little work.
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
# Class to be assigned to a thread.
# This should be subclassed to provide new functionality.
class GenericLoop(QtCore.QObject):
def __init__(self):
super(GenericLoop, self).__init__()
# We use this signal to tell the main thread
# when this thread is finished.
finished_Sig = QtCore.pyqtSignal()
# Default timeout is 0, i.e. do work on thread after
# other events have been dealt with
__timeout = 0
__processTimer = None
__args = None
__kwargs = None
# We use this function to set the arguments used by run(),
# if we want to change them mid-execution
#QtCore.pyqtSlot(tuple, dict)
def changeArgs(self, args, kwargs):
self.__args = args
self.__kwargs = kwargs
# We can change the timeout used to make the thread run
# at given intervals. Note that the timing is not exact,
# since this is impossible with a real time operating system
#QtCore.pyqtSlot(int)
def setTimeout(self, mseconds):
self.__timeout = int(mseconds)
# Call either a singleShot QTimer (one execution),
# or a normal QTimer (repeated), to start the loop
#QtCore.pyqtSlot(bool, tuple, dict)
def startTimer(self, singleShot, args, kwargs):
self.__processTimer = QtCore.QTimer()
# We can't pass args and kwargs directly because QTimer.timeout
# emits a signal with allowing no contained variables
# so we copy args and kwargs to local variables instead
self.changeArgs(args, kwargs)
if singleShot:
self.__processTimer.singleShot(self.__timeout, self.callRun)
else:
self.__processTimer.timeout.connect(self.callRun)
self.__processTimer.start(self.__timeout)
# Call finish from within subclass using self.finish(), or
# from another thread using signals. finish() will stop the
# QTimer causing execution of the loop. The loop can be started again
# by calling startTimer() or stopTimer() from another thread
#QtCore.pyqtSlot()
def stopTimer(self):
if self.__processTimer.isActive():
self.__processTimer.stop()
else:
print "ERROR: stopTimer() has been called but no timer is running!"
# We call this to delete the thread.
#QtCore.pyqtSlot()
def deleteThread(self):
self.finished_Sig.emit()
# This calls run(), in order to enable the passing of
# command line arguments to the loop
#QtCore.pyqtSlot()
def callRun(self):
self.run(self.__args, self.__kwargs)
# run() can be called directly from another thread if required
#QtCore.pyqtSlot(tuple, dict)
def run(self, args, kwargs):
print "ERROR: run() has not been defined! Stopping thread..."
self.stopTimer()
# Class for creating threads
class GenericThread(QtCore.QObject):
# Private variables include the thread.
__sendArguments_Sig = QtCore.pyqtSignal(tuple, dict)
__startTimer_Sig = QtCore.pyqtSignal(int, tuple, dict)
__setTimeout_Sig = QtCore.pyqtSignal(int)
__obj = None
__finished_Sig = None
__thread = QtCore.QThread()
# Object to be threaded must be specified when
# creating a GenericThread object
def __init__(self, obj):
super(GenericThread, self).__init__()
self.__obj = obj
self.moreInit()
# Set up object on thread
def moreInit(self):
self.__thread = QtCore.QThread()
self.__obj.moveToThread(self.__thread)
# Allows thread to delete itself when done
self.__obj.finished_Sig.connect(self.__thread.deleteLater)
self.__sendArguments_Sig.connect(self.__obj.changeArgs)
self.__startTimer_Sig.connect(self.__obj.startTimer)
self.__setTimeout_Sig.connect(self.__obj.setTimeout)
self.__thread.start()
# Sets the QTimer timeout and does some checking
# to make sure that types are as they should be
def setTimeout(self, mseconds):
if mseconds >= 0 and type(mseconds) is type(int()):
self.__setTimeout_Sig.emit(mseconds)
elif mseconds < 0 and type(mseconds) is type(int()):
print "Error: timeout of below 0 ms specified."
else:
print "Error: timeout period is specified with a type other than int."
# Starts a function in the thread via signals, and can pass
# it arguments if required. Function executes until QTimer is stopped
def startLoop(self, *args, **kwargs):
if (self.__thread == None):
print "ERROR: Thread has been deleted!"
else:
self.__startTimer_Sig.emit(False, args, kwargs)
# Starts a function in the thread via signals, once
def startOnce(self, *args, **kwargs):
if (self.__thread == None):
print "ERROR: Thread has been deleted!"
else:
self.__startTimer_Sig.emit(True, args, kwargs)
# Calls a very simple GUI just to show that the program is responsive
class GUIBox(QtGui.QWidget):
def __init__(self):
super(GUIBox, self).__init__()
self.initUI()
def initUI(self):
self.resize(250, 150)
self.setWindowTitle('Threading!')
self.show()
# Subclass GenericLoop to reimplement run and such.
class SubClassedLoop(GenericLoop):
def __init__(self):
super(SubClassedLoop, self).__init__()
__i = 0
#QtCore.pyqtSlot(tuple, dict)
def run(self, args, kwargs):
if self.__i>=50:
self.stopTimer()
return
print self.__i, args
self.__i += 1
app = QtGui.QApplication(sys.argv)
ex = GUIBox()
# Create 3 worker objects to do the actual calculation
worker1 = SubClassedLoop()
worker2 = SubClassedLoop()
worker3 = SubClassedLoop()
# Create 3 thread managing objects to do the thread control
thread1 = GenericThread(worker1)
thread2 = GenericThread(worker2)
thread3 = GenericThread(worker3)
# Set the threads to execute as soon as there is no work to do
thread1.setTimeout(125)
thread2.setTimeout(125)
thread3.setTimeout(125)
# Start threads
thread1.startLoop(1)
thread2.startLoop(2)
thread3.startLoop(3)
# Quit the program when the GUI window is closed
sys.exit( app.exec_() )

Related

How to prevent the main window from freezing using PyQt5? [duplicate]

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

PyQt4: How to pause a Thread until a signal is emitted?

I have the following pyqtmain.py:
#!/usr/bin/python3
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from pyqtMeasThread import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
self.qt_app = QApplication(sys.argv)
QMainWindow.__init__(self, parent)
buttonWidget = QWidget()
rsltLabel = QLabel("Result:")
self.rsltFiled = QLineEdit()
self.buttonStart = QPushButton("Start")
verticalLayout = QVBoxLayout(buttonWidget)
verticalLayout.addWidget(rsltLabel)
verticalLayout.addWidget(self.rsltFiled)
verticalLayout.addWidget(self.buttonStart)
butDW = QDockWidget("Control", self)
butDW.setWidget(buttonWidget)
self.addDockWidget(Qt.LeftDockWidgetArea, butDW)
self.mthread = QThread() # New thread to run the Measurement Engine
self.worker = MeasurementEngine() # Measurement Engine Object
self.worker.moveToThread(self.mthread)
self.mthread.finished.connect(self.worker.deleteLater) # Cleanup after thread finished
self.worker.measure_msg.connect(self.showRslt)
self.buttonStart.clicked.connect(self.worker.run)
# Everything configured, start the worker thread.
self.mthread.start()
def run(self):
""" Show the window and start the event loop """
self.show()
self.qt_app.exec_() # Start event loop
#pyqtSlot(str)
def showRslt(self, mystr):
self.rsltFiled.setText(mystr)
def main():
win = MainWindow()
win.run()
if __name__ == '__main__':
main()
And another thread script performing the actual measurement:
from PyQt4.QtCore import *
import time
class MeasurementEngine(QObject):
measure_msg = pyqtSignal(str)
def __init__(self):
QObject.__init__(self) # Don't forget to call base class constructor
#pyqtSlot()
def run(self):
self.measure_msg.emit('phase1')
time.sleep(2) # here I would like to make it as an interrupt
self.measure_msg.emit('phase2')
What this code does now is that after the Start button is pressed, the function run in the thread will be executed. However, actually in the function run, there are two phases of the measurement. Right now I used an time delay.
But what I would like to implement actually is that after the 'phase1' measurement is done. A message box will be popped up, and at the same time, the thread will be paused/held. Until the user closed the message box, then the thread function will be resumed.
Use a QWaitCondition from the QtCore module. Using a mutex lock, you set the background thread to wait/sleep until the foreground thread wakes it back up. Then it will continue doing its work from there.
#!/usr/bin/python3
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from pyqtMeasThread import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
self.qt_app = QApplication(sys.argv)
QMainWindow.__init__(self, parent)
buttonWidget = QWidget()
rsltLabel = QLabel("Result:")
self.rsltFiled = QLineEdit()
self.buttonStart = QPushButton("Start")
verticalLayout = QVBoxLayout(buttonWidget)
verticalLayout.addWidget(rsltLabel)
verticalLayout.addWidget(self.rsltFiled)
verticalLayout.addWidget(self.buttonStart)
butDW = QDockWidget("Control", self)
butDW.setWidget(buttonWidget)
self.addDockWidget(Qt.LeftDockWidgetArea, butDW)
self.mutex = QMutex()
self.cond = QWaitCondition()
self.mthread = QThread() # New thread to run the Measurement Engine
self.worker = MeasurementEngine(self.mutex, self.cond) # Measurement Engine Object
self.worker.moveToThread(self.mthread)
self.mthread.finished.connect(self.worker.deleteLater) # Cleanup after thread finished
self.worker.measure_msg.connect(self.showRslt)
self.buttonStart.clicked.connect(self.worker.run)
# Everything configured, start the worker thread.
self.mthread.start()
def run(self):
""" Show the window and start the event loop """
self.show()
self.qt_app.exec_() # Start event loop
# since this is a slot, it will always get run in the event loop in the main thread
#pyqtSlot(str)
def showRslt(self, mystr):
self.rsltFiled.setText(mystr)
msgBox = QMessageBox(parent=self)
msgBox.setText("Close this dialog to continue to Phase 2.")
msgBox.exec_()
self.cond.wakeAll()
def main():
win = MainWindow()
win.run()
if __name__ == '__main__':
main()
And:
from PyQt4.QtCore import *
import time
class MeasurementEngine(QObject):
measure_msg = pyqtSignal(str)
def __init__(self, mutex, cond):
QObject.__init__(self) # Don't forget to call base class constructor
self.mtx = mutex
self.cond = cond
#pyqtSlot()
def run(self):
# NOTE: do work for phase 1 here
self.measure_msg.emit('phase1')
self.mtx.lock()
try:
self.cond.wait(self.mtx)
# NOTE: do work for phase 2 here
self.measure_msg.emit('phase2')
finally:
self.mtx.unlock()
Your timing is a little bit off in all this though. You create the app and start the thread before you even show your window. Thus, the message box will pop up before the main window even pops up. To get the right sequence of events, you should start your thread as part of the run method of your MainWindow, after you have already made the main window visible. If you want the wait condition to be separate from the setting of the messages, you may need a separate signal and slot to deal with that.
You can't display a QDialog from within a QThread. All GUI related stuff must be done in the GUI thread (the one that created the QApplication object). What you could do is to use 2 QThread:
1st: perform phase1. You can connect the finished signal of this QThread to a slot in the QMainWindow that will display the popup (using QDialog.exec_() so it will be modal).
2nd: perform phase2. You create the QThread after the popup shown here above has been closed.
Your thread can emit a signal to the main window to show the dialog.
If you don't want to close the thread while the dialog is open, the thread could enter a while loop for waiting. In the while loop it can continuously check a variable which the main thread can set to true after the dialog is finished.
This might not be the cleanest solution, but it should work.
To clarify my answer a bit, I added some pseudo code. What you have to care about is how you share the dialog_closed variable. You could e.g. use a member variable of the thread class.
Thread:
emit_signal
dialog_closed = False
while not dialog_closed:
pass
go_on_with_processing
MainThread:
def SignalRecieved():
open_dialog
dialog_closed = True
I recently had to solve pretty much this problem, did a little research and discovered an elegant technique that seems to work reliably. I didn't need the full complexity detailed there, so here's an outline of the steps I took.
My GUI class defines, as class attributes, two signals.
oyn_sig = pyqtSignal(str) # Request for operator yes/no
ryn_sig = pyqtSignal(bool) # Response to yes/no request
Inside the method that initialises the GUI components this signal is connected to the GUI instance's signal handler.
self.oyn_sig.connect(self.operator_yes_no)
Here's the code for the handler method of the GUI:
#pyqtSlot(str)
def operator_yes_no(self, msg):
"Asks the user a `yes/no question on receipt of a signal then signal a bool answer.`"
answer = QMessageBox.question(None,
"Confirm Test Sucess",
msg,
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
# Signal the caller that the result was received.
self.ryn_sig.emit(answer==QMessageBox.Yes)
As usual the GUI is running in the main thread, and so it needs to be signalled from the thread doing the work in the background. In turn, once it's received the operator's response it raises a response signal to the originating thread.
The worker thread uses the following function to get an operator response.
def operator_yes_no(self, msg):
loop = LoopSpinner(self.gui, msg)
loop.exec_()
return loop.result
This creates a LoopSpinner object and starts executing its event loop, thereby suspend the current thread's event loop until the "inner thread" terminates. Most of the smarts are hidden inside the LoopSpinner class, which should probably have been better named. Here's its definition.
class LoopSpinner(QEventLoop):
def __init__(self, gui, msg):
"Ask for an answer and communicate the result."
QEventLoop.__init__(self)
gui.ryn_sig.connect(self.get_answer)
gui.oyn_sig.emit(msg)
#pyqtSlot(bool)
def get_answer(self, result):
self.result = result
self.quit()
A LoopSpinner instance connects the response signal to its get_answer method and emits the question signal. When the signal is received the answer is stored as an attribute value and the loop quits. The loop is still referenced by its caller, which can safely access the result attribute before the instance is garbage collected.

PyQt thread communication help? QThread and QObject

After read and searching I am trying to use the generate a QObject then use the movetoThread method to run an independent process and allow the QMainWindow to continue to respond. This has not worked when I have tried to implement the operation in a QThread.run() method. The following code is my attempt to make a simple example. While the code works in running thread independent of the MainWindow, it does not abort. The only way I can get a thread to stop is to set worker.end = True. Which I think should not be the way to do it.
"""
This is a program to test Threading with Objects in PyQt4.
"""
from time import sleep
import sys
from PyQt4.QtCore import QObject, pyqtSlot, pyqtSignal, QThread
from PyQt4.QtGui import QMainWindow, QApplication, QProgressBar
from PyQt4.QtGui import QPushButton, QVBoxLayout, QWidget
class workerObject(QObject):
bar_signal = pyqtSignal(int)
res_signal = pyqtSignal(str)
term_signal = pyqtSignal()
def __init__(self, maxIters):
super(workerObject, self).__init__()
self.maxIters = maxIters
def run(self):
self.bar_signal.emit(self.maxIters)
sleep(1)
self.end = False
for step in range(self.maxIters):
if self.end:
self.maxIters = step
break
self.bar_signal.emit(step)
sleep(2)
self.res_signal.emit("Got to {}".format(self.maxIters))
self.term_signal.emit()
#pyqtSlot()
def mystop(self):
print "stop signalled?"
self.end = True
class MCwindow(QMainWindow):
abort_signal = pyqtSignal(name='abort_signal')
def __init__(self):
super(MCwindow,self).__init__()
self.maxIters = 50
widget = QWidget()
layout = QVBoxLayout(widget)
self.go_btn = QPushButton()
self.go_btn.setText('Go')
layout.addWidget(self.go_btn)
self.abort_btn = QPushButton()
self.abort_btn.setText('Stop')
layout.addWidget(self.abort_btn)
self.simulation_bar = QProgressBar()
self.simulation_bar.setRange(0, self.maxIters)
self.simulation_bar.setFormat("%v")
layout.addWidget(self.simulation_bar)
self.setCentralWidget(widget)
self.go_btn.clicked.connect(self.run_mc)
# The button calls the windows method to stop --- it could
# be that is 'clicked' calls the worker.mystop
# self.abort_btn.clicked.connect(self.stop_mc)
# This allows for the abort button to do somethign in the MainWindow
# before the abort_signal is sent, this works
self.abort_btn.clicked.connect(self.stop_mc)
def run_mc(self):
self.thread = QThread()
self.worker = workerObject(self.maxIters)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
# This is the simple stop method, but does not work
# self.abort_btn.clicked.connect(self.worker.mystop)
# This uses the signal in the MCwindow - this connection does NOT works
self.abort_signal.connect(self.worker.mystop)
# This does NOT stop the thread
# and would not allow for any clean up in the worker.
# self.abort_signal.connect(self.thread.terminate)
# This is a 'bad' way to stop the woker ... It does, however, work
# self.abort_signal.connect(self.stopper)
self.worker.bar_signal.connect(self.setBar)
self.worker.res_signal.connect(self.setData)
self.worker.term_signal.connect(self.thread.terminate)
self.thread.start()
def stop_mc(self):
print "Stopping?!"
# This signal is NEVER seen by the Worker.
self.abort_signal.emit()
def stopper(self):
print "I should stop?!"
# Should use signals to tell the worker to stop - and not setting a attribute
self.worker.end=True
#pyqtSlot(int)
def setBar(self, val):
self.simulation_bar.setValue(val)
#pyqtSlot(str)
def setData(self, txt):
print "Got done Sig!", txt
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MCwindow()
window.show()
sys.exit(app.exec_())
The reason why the slot connected to abort_signal doesn't seem to get called, is because cross-thread signals are queued by default. This means the signal will be wrapped as an event and posted to the event queue of whichever thread the receiver is living in.
In your particular example, the receiver is a worker object which has been moved to a worker thread. Calling start() on the worker thread will start its event-loop, and that is where abort_signal will be queued. However, the run() method of the worker object starts a for loop, which will block the thread's event processing in exactly the same way it would if it was executed in the main gui thread!
You can more clearly see what's happening if you make a few adjustments to your example:
class MCwindow(QMainWindow):
abort_signal = pyqtSignal(name='abort_signal')
def __init__(self):
super(MCwindow,self).__init__()
# use a sane default
self.maxIters = 5
...
# DO NOT use QThread.terminate
self.worker.term_signal.connect(self.thread.quit)
Now run the example, and then click the Go button, click the Stop button, and wait for the worker to complete normally. This should produce output like this:
Stopping?!
Got done Sig! Got to 5
stop signalled?
Note that "stop signalled" is output last - i.e. after run() exits and control has returned to the thread's event-loop. In order to process in-coming signals while the worker is running, you will need to force immediate processing of the thread's pending events. This can be done like this:
for step in range(self.maxIters):
QApplication.processEvents()
...
With that in place, you should then see output like this:
Stopping?!
stop signalled?
Got done Sig! Got to 2
Which is presumably what you intended.
Typically a thread will close when it exits the run method. The other way to get a regular python thread to close is by calling it's join method.
For PyQt the join method should either be the quit or terminate method. You should probably still set your end variable to True.

pyqt QThread blocking main thread

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

Background thread with QThread in PyQt

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

Categories

Resources