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 am building a system to connect Arduino-based sensors to a RPi via Bluetooth LE, and display the info (temperature and battery life) on a GUI. I have two main classes in my program, one that manages the GUI, and one that manages the BLE connection(class HubSensor). A HubSensor object takes the MAC address for each sensor and is supposed to emit a signal with an attached tuple that contains the temp sensed, battery life, and an index integer to let the main program know which sensor it is. HubSensor gets it's information once per second, and should be sending out the signal every time. (There is input validation already built but it's not relevant to my question.) Most of this is working fine so far.
My problem is I can't figure out how to create a slot to receive the signal so it can update the display (and later keep a log in a CSV file). I'm using the BluePy library to manage the BLE connection, which for me has it's own additional challenges.
So, this is how my program works (I think). Each thread (since I have multiple sensors) creates a HubSensor object. When the object makes the BLE connection, it then creates a MyDelegate object (subclassed from BluePy's DefaultDelegate. Inside the MyDelegate object, the Qt Signal is emitted. I need access to that signal outside of all of those classes and since I don't know the name of the MyDelegate object created, I don't know how to get to it.
I've tried having each of the above mentioned classes inherit each others' characteristics, but I'm not sure I did it right.
From trailerTempDisplay.py
import sys
from PyQt5.QtCore import *
from HubSensor import *
from PyQt5 import QtWidgets, uic
from bluepy.btle import *
from datetime import datetime
# mac addresses for the sensors. later, this will need a function to allow new devices to connect
bt_addrs = ['c1:49:02:59:ae:50', 'f3:ad:ed:46:ea:16']
app = QtWidgets.QApplication(sys.argv)
class Worker(QRunnable):
def __init__(self, macAddress, ind):
super(Worker, self).__init__()
self.macAddress = macAddress
self.ind = ind
#pyqtSlot()
#this is where each sensor exists. each object is created and runs here
def run(self):
self.sensor = HubSensor(self.macAddress, self.ind)
self.sensor.notified.connect(self.updateValues())
#close button
def buttonClicked():
app.closeAllWindows()
window = uic.loadUi("mainwindow.ui")
window.pushButton.clicked.connect(buttonClicked)
def updateValues(self):
print("value updated") # debugging
window.show()
window.threadpool = QThreadPool()
index = 0
for addr in bt_addrs:
worker = Worker(addr, index)
index += 1
window.threadpool.start(worker)
app.exec()
From HubSensor.py
from bluepy.btle import *
from PyQt5.QtCore import QObject, pyqtSignal
class MyDelegate(DefaultDelegate, QObject):
def __init__(self, index):
DefaultDelegate.__init__(self)
QObject.__init__(self)
self.index = index
# class variable for the notified signal
notified = pyqtSignal(tuple)
def handleNotification(self, cHandle, data):
# exception handling prevents bad data from being passed. cHandle is not used but might be useful later
try:
# defining the sensorData tuple. 1, 2, and 3 are dummy values
self.sensorData = (1, 2, 3)
self.notified.emit(self.sensorData) # this should emit a signal every time the function is called and send a tuple with temp, battery, and sensor index(id)
except (ValueError, IndexError):
pass
class HubSensor(MyDelegate):
# constructor. connects to device defined by mac address and position.
# uuid is static and should not change
def __init__(self, mac, index):
self.index = index # keeps track of sensor position
self.mac = mac
self.p = Peripheral(self.mac, 'random') # adafruit feathers must be 'random' for some reason
self.p.setDelegate(MyDelegate(self.index))
self.p.writeCharacteristic(35, b'\x01\x00') # writing these bits to handle '35' enables notifications
while True:
if self.p.waitForNotifications(1):
# when a bluetooth notification is received, handleNotification is called
continue
When I run the program, "value updated" is not displayed on the console. It should pop up about twice per second and just repeat. Later, I will add in the part that turns the values passed into the GUI display.
Let me apologize in advance because I am still very much a beginner. I think I've included all the relevant parts of my code, but I don't know for sure. Also, I'm pretty sure my terminology in some spots is incorrect, so I hope you all can decipher what I actually mean. Thank you in advance for any help you can give me!
Your code is confusing, for example that HubSensor is a delegate whose attribute is a Peripheral and that Peripheral has another delegate, among other problems.
So instead of relying on your code, I created the PeripheralManager class that will notify the information received by the assigned peripheral through a signal. In the case of the infinite loop, it will be handled in a thread using threading.Thread.
import threading
from PyQt5 import QtCore, QtWidgets, uic
from bluepy.btle import DefaultDelegate, Peripheral, BTLEDisconnectError
class PeripheralManager(QtCore.QObject, DefaultDelegate):
notified = QtCore.pyqtSignal(bytes)
def __init__(self, peripheral, parent=None):
super().__init__(parent)
self._peripheral = peripheral
self.peripheral.setDelegate(self)
self.peripheral.writeCharacteristic(35, b"\x01\x00")
threading.Thread(target=self._manage_notifications, daemon=True).start()
#property
def peripheral(self):
return self._peripheral
def handleNotification(self, cHandle, data):
self.notified.emit(self.data)
def _manage_notifications(self):
while self.peripheral.waitForNotifications(1):
continue
def buttonClicked():
QtWidgets.QApplication.closeAllWindows()
def updateValues(values):
print("value updated", values)
def main(args):
app = QtWidgets.QApplication(args)
bt_addrs = ["c1:49:02:59:ae:50", "f3:ad:ed:46:ea:16"]
managers = []
for addr in bt_addrs:
try:
p = Peripheral(addr, "random")
except BTLEDisconnectError as e:
print(e)
else:
manager = PeripheralManager(p)
manager.notified.connect(updateValues)
managers.append(manager)
window = uic.loadUi("mainwindow.ui")
window.pushButton.clicked.connect(buttonClicked)
window.show()
ret = app.exec_()
return ret
if __name__ == "__main__":
import sys
sys.exit(main(sys.argv))
I'm attempting to use QThreads to update my custom tool's Qt-based UI inside of Maya. I have a thread that executes arbitrary methods and returns the result via an emitted signal, which I then use to update my UI. Here's my custom QThread class:
from PySide import QtCore
class Thread(QtCore.QThread):
result = QtCore.Signal(object)
def __init__(self, parent, method, **kwargs):
super(Thread, self).__init__(parent)
self.parent = parent
self.method = method
self.kwargs = kwargs
def run(self):
result = self.method(**self.kwargs)
self.result.emit(result)
The methods I'm passing to the thread are basic requests for getting serialized data from a web address, for example:
import requests
def request_method(address):
request = requests.get(address)
return request.json()
And here is how I use the thread in my custom tool to dynamically update my UI:
...
thread = Thread(parent=self, method=request_method, address='http://www.example.com/')
thread.result.connect(self._slot_result)
thread.start()
def _slot_result(self, result):
# Use the resulting data to update some UI element:
self.label.setText(result)
...
This workflow works in other DCCs like Nuke, but for some reason it causes Maya to sometimes crash inconsistently. No error message, no log, just a hard crash.
This makes me think that my QThread workflow design is obviously not Maya-friendly. Any ideas how best to avoid crashing Maya when using QThreads and what may be causing this particular issue?
This doesn't answer directly what's going on with your QThread, but to show you another way to go about threading with guis in Maya.
Here's a simple example of a gui that has a progress bar and a button. When the user clicks the button it will create a bunch of worker objects on a different thread to do a time.sleep(), and will update the progress bar as they finish. Since they're on a different thread it won't lock the user from the gui so they can still interact with it as it updates:
from functools import partial
import traceback
import time
from PySide2 import QtCore
from PySide2 import QtWidgets
class Window(QtWidgets.QWidget):
"""
Your main gui class that contains a progress bar and a button.
"""
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# Create our main thread pool object that will handle all the workers and communication back to this gui.
self.thread_pool = ThreadPool(max_thread_count=5) # Change this number to have more workers running at the same time. May need error checking to make sure enough threads are available though!
self.thread_pool.pool_started.connect(self.thread_pool_on_start)
self.thread_pool.pool_finished.connect(self.thread_pool_on_finish)
self.thread_pool.worker_finished.connect(self.worker_on_finish)
self.progress_bar = QtWidgets.QProgressBar()
self.button = QtWidgets.QPushButton("Run it")
self.button.clicked.connect(partial(self.thread_pool.start, 30)) # This is the number of iterations we want to process.
self.main_layout = QtWidgets.QVBoxLayout()
self.main_layout.addWidget(self.progress_bar)
self.main_layout.addWidget(self.button)
self.setLayout(self.main_layout)
self.setWindowTitle("Thread example")
self.resize(500, 0)
def thread_pool_on_start(self, count):
# Triggers right before workers are about to be created. Start preparing the gui to be in a "processing" state.
self.progress_bar.setValue(0)
self.progress_bar.setMaximum(count)
def thread_pool_on_finish(self):
# Triggers when all workers are done. At this point you can do a clean-up on your gui to restore it to it's normal idle state.
if self.thread_pool._has_errors:
print "Pool finished with no errors!"
else:
print "Pool finished successfully!"
def worker_on_finish(self, status):
# Triggers when a worker is finished, where we can update the progress bar.
self.progress_bar.setValue(self.progress_bar.value() + 1)
class ThreadSignals(QtCore.QObject):
"""
Signals must inherit from QObject, so this is a workaround to signal from a QRunnable object.
We will use signals to communicate from the Worker class back to the ThreadPool.
"""
finished = QtCore.Signal(int)
class Worker(QtCore.QRunnable):
"""
Executes code in a seperate thread.
Communicates with the ThreadPool it spawned from via signals.
"""
StatusOk = 0
StatusError = 1
def __init__(self):
super(Worker, self).__init__()
self.signals = ThreadSignals()
def run(self):
status = Worker.StatusOk
try:
time.sleep(1) # Process something big here.
except Exception as e:
print traceback.format_exc()
status = Worker.StatusError
self.signals.finished.emit(status)
class ThreadPool(QtCore.QObject):
"""
Manages all Worker objects.
This will receive signals from workers then communicate back to the main gui.
"""
pool_started = QtCore.Signal(int)
pool_finished = QtCore.Signal()
worker_finished = QtCore.Signal(int)
def __init__(self, max_thread_count=1):
QtCore.QObject.__init__(self)
self._count = 0
self._processed = 0
self._has_errors = False
self.pool = QtCore.QThreadPool()
self.pool.setMaxThreadCount(max_thread_count)
def worker_on_finished(self, status):
self._processed += 1
# If a worker fails, indicate that an error happened.
if status == Worker.StatusError:
self._has_errors = True
if self._processed == self._count:
# Signal to gui that all workers are done.
self.pool_finished.emit()
def start(self, count):
# Reset values.
self._count = count
self._processed = 0
self._has_errors = False
# Signal to gui that workers are about to begin. You can prepare your gui at this point.
self.pool_started.emit(count)
# Create workers and connect signals to gui so we can update it as they finish.
for i in range(count):
worker = Worker()
worker.signals.finished.connect(self.worker_finished)
worker.signals.finished.connect(self.worker_on_finished)
self.pool.start(worker)
def launch():
global inst
inst = Window()
inst.show()
Aside from the main gui, there's 3 different classes.
ThreadPool: This is responsible to create and manage all worker objects. This class is also responsible to communicate back to the gui with signals so it can react accordingly while workers are completing.
Worker: This is what does the actual heavy lifting and whatever you want to process in the thread.
ThreadSignals: This is used inside the worker to be able to communicate back to the pool when it's done. The worker class isn't inherited by QObject, which means it can't emit signals in itself, so this is used as a work around.
I know this all looks long winded, but it seems to be working fine in a bunch of different tools without any hard crashes.
One of the engineers at our studio discovered a few bugs related to the use of Python threads and PyQt/PySide. Please refer to:
[PySide 1.x] https://bugreports.qt.io/browse/PYSIDE-810
[PySide 2.x] https://bugreports.qt.io/browse/PYSIDE-813
Notes from the reporter:
Although QObject is reentrant, the GUI classes, notably QWidget and all its subclasses, are not reentrant. They can only be used from the main thread.
I am relatively new to python, but was able to get a reasonably useful program to run to crunch a lot of data. I am able to run it over multiple sets of data sequentially using another python script to call the program serially, but I wanted to create a GUI and use multithreading to allow others to use it without knowing all the ins and outs of programming. I created the GUI successfully, and can feed data bidirectionally using signals and slots. What I am having trouble with is creating multiple threads with the same function.
I have done some research and it appears that the function needs to be threadsafe, and unfortunately mine is not because I am using curve_fit() from scipy, which is not threadsafe. So, based on what I have read in this forum and others, I should be using mutex.lock(), but I get the "SystemError: null argument to internal routine" when calling curve_fit()
Here is some sample code to demonstrate what I have done:
import sip
sip.setapi('QString', 2)
import sys, time
from PyQt5 import QtCore, QtGui, uic, QtWidgets
from ZthCalculation import ZthObject
qtCreatorFile = "PyQtZthUI_01.ui" # Enter file here.
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
#class MyApp(QtGui.QMainWindow, Ui_MainWindow):
class MyApp(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
QtWidgets.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.RunButton.clicked.connect(self.RunZthTest)
.
.
.
def RunZthTest(self):
#create as processes instead of threads???
# self.Process1 = QtCore.QProcess()
self.Thread1 = QtCore.QThread()
self.obj1 = ZthObject(self.InputWet1.text(), self.InputDry1.text(), self.Output1.text(), self.side1)
self.obj1.moveToThread(self.Thread1)
self.Thread1.started.connect(self.obj1.ZthCalculation)
self.obj1.textBox.connect(self.updateTextBox1)
self.signal1 = self.obj1.finished.connect(self.Thread1.quit)
self.Thread1.setObjectName("Thread1")
self.Thread1.start()
time.sleep(.1)
self.Thread2 = QtCore.QThread()
self.obj2 = ZthObject(self.InputWet2.text(), self.InputDry2.text(), self.Output2.text(), self.side2)
self.obj2.moveToThread(self.Thread2)
self.Thread2.started.connect(self.obj2.ZthCalculation)
self.obj2.textBox.connect(self.updateTextBox2)
self.signal2 = self.obj2.finished.connect(self.Thread2.quit)
self.Thread2.setObjectName("Thread2")
self.Thread2.start()
time.sleep(.1)
self.Thread3 = QtCore.QThread()
self.obj3 = ZthObject(self.InputWet3.text(), self.InputDry3.text(), self.Output3.text(), self.side3)
self.obj3.moveToThread(self.Thread3)
self.Thread3.started.connect(self.obj3.ZthCalculation)
self.obj3.textBox.connect(self.updateTextBox3)
self.signal3 = self.obj3.finished.connect(self.Thread3.quit)
self.Thread3.setObjectName("Thread3")
self.Thread3.start()
.
.
.
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MyApp()
window.show()
# sys.exit(app.exec_())
app.exec_()
In another file, I have the main function that I am calling as a thread:
class ZthObject(QtCore.QObject):
killthread = False
finished = QtCore.pyqtSignal()
textBox = QtCore.pyqtSignal(str)
def __init__(self, wetFilePath, dryFilePath, outFilePath, side, parent=None):
super(self.__class__, self).__init__()
self.wetFilePath = wetFilePath
self.dryFilePath = dryFilePath
self.outFilePath = outFilePath
self.side = side
self.mutex = QtCore.QMutex()
def cleanup(self):
ZthObject.killthread = True
# def ZthCalculation(self, wetFilePath, dryFilePath, outFilePath, side):
def ZthCalculation(self):
#calculations here
.
.
.
print("waypoint2")
self.mutex.lock()
popt, pcov = curve_fit(Foster6, timeShort, ZthjcShort, p0 = [Rs, taus])
self.mutex.unlock()
.
.
.
self.finished.emit()
I can successfully run the code only calling one thread, but if I call multiple threads, then the output window prints out 'waypoint2' for each thread called, then crashes with the system error I mentioned above.
What am I doing wrong? Do I need to use separate processes instead of Qthreads? Am I misunderstanding how threads work? I want them to operate in independent variable spaces.
Using a mutex really only makes something thread safe if all of the other things sharing the internals of the function also respects the mutex. In this case, it won't because, while using a mutex prevents simultaneous calls to curve_fit, you don't know what it is that is thread unsafe about the function, and so you can't be sure that something else won't also use the thread unsafe bit of code at the same time in another thread (e.g. the main thread).
Coupled with the fact that the Python GIL prevents true threading (threading only provides a speed boost in Python if your task is IO bound rather than CPU bound), I would suggest moving to a multiprocess model.
How can I introduce a text in a lineEdit from a thread that are getting the data whithout colapse the program? The important line is in the class "fil" where it shows Principal.self.aplicacio.actual_lineEdit.setText(self.temp)
# !/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import serial
import threading
from time import sleep
from PyQt4 import QtCore, QtGui
from temperaturaUI import Ui_Form
class Principal(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.aplicacio = Ui_Form()
self.aplicacio.setupUi(self)
self.aplicacio.sortir_Button.clicked.connect(exit)
self.aplicacio.connectar_Button.clicked.connect(self.connectar)
def connectar(self):
try:
arduino = serial.Serial('/dev/ttyACM0', 9600)
print "Connectat amb èxit"
temperatura = fil(0, arduino, self.aplicacio.actual_lineEdit)
temperatura.start()
except:
print "Impossible connectar a l'Arduino"
class fil(threading.Thread):
def __init__(self, temp, serie, line):
threading.Thread.__init__(self)
self.temp = temp
self.serie = serie
self.line = line
def run(self):
try:
while 1:
self.temp = self.serie.readline()
if self.temp != 0:
**Principal.self.aplicacio.actual_lineEdit.setText(self.temp)**
sleep(0.2)
except:
print "Error al llegir de l'Arduino"
def main():
app = QtGui.QApplication(sys.argv)
aplicacio = Principal()
aplicacio.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You can use signals. You would add a signal to the fil class that emits the new text:
class fil(threading.Thread):
update_line_edit = pyqtSignal(str)
def __init__(self, temp, serie, line):
...
def run(self):
try:
while True:
self.temp = self.serie.readline()
if not self.temp:
update_line_edit.emit(self.temp)
...
Then, simply connect that signal to a slot function in your Principal class:
class Principal(QtGui.QWidget):
def __init__(self):
...
def connectar(self):
try:
arduino = serial.Serial('/dev/ttyACM0', 9600)
print "Connectat amb èxit"
temperatura = fil(0, arduino, self.aplicacio.actual_lineEdit)
temperatura.change_line_edit.connect(self.update_line_edit)
...
def update_line_edit(self, text):
self.aplicacio.actual_lineEdit.setText(text)
There are a few ways to do this correctly.
The first is to use a QThread instead of a python thread. You can then use Qt signals to pass a message back from the fil thread to the Qt MainThread and append the message to the QLineEdit there. Another similar approach is to continue using a Python thread, but place your message in a Python Queue.Queue() object. This Queue is then read by a secondary QThread, whose sole purpose is to read messages out of the Queue and emit a signal back to the MainThread.
The common feature of these two methods is that you only access Qt GUI objects from the MainThread and use signals/slots to communicate between threads. Here are some other questions where I've answered similar questions (you should be able to adapt them to your program):
Redirecting stdout and stderr to a PyQt4 QTextEdit from a secondary thread
Syncing activity in PyQt QThreads
However, since answering those questions, my colleagues and I have created a project that helps simplify writing multi-threaded Qt applications. The project is called qtutils and is on PyPi so it can be installed with pip or easy_install (just run pip install qtutils or easy_install qtutils from a commandline/terminal window).
This library has (among others) some functions inmain and inmain_later which will run a specified method in the Qt MainThread (regardless of the thread the call is made from) synchronously or asynchronously. Documentation on how to use these methods is here. I've modified your example code to use my inmain method and put the code here: http://pastebin.com/QM1Y6zBx -- obviously you need to install qtutils for it to work!