PyQt signals between threads not emitted - multithreading

I am stuck. It should be easy and I have done it many times using the C++ API of Qt however for some reason several of my signals/slots are not working when I'm doing this in PyQt (I've recently started with the concept of a worker QObject in PyQt). I believe it has to do something with the separate thread I'm emitting my signals to/from.
from PyQt4.QtCore import QThread, QObject, pyqtSignal, pyqtSlot, QTimer
from PyQt4.QtGui import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel
class Slave(QObject):
countSignal = pyqtSignal(int)
def __init__(self, parent = None):
super(Slave, self).__init__()
self.toggleFlag = False
self.counter = 0
#pyqtSlot()
def work(self):
if not self.toggleFlag: return
if self.counter > 10: self.counter = 0
self.counter += self.counter
self.countSignal.emit(self.counter)
#pyqtSlot()
def toggle(self):
self.toggleFlag = not self.toggleFlag
class Master(QWidget):
toggleSignal = pyqtSignal()
def __init__(self, parent = None):
super(Master, self).__init__()
self.initUi()
self.setupConn()
def __del__(self):
self.thread.quit()
while not self.thread.isFinished(): pass
def initUi(self):
layout = QVBoxLayout()
self.buttonToggleSlave = QPushButton('Start')
self.labelCounterSlave = QLabel('0')
layout.addWidget(self.buttonToggleSlave)
layout.addWidget(self.labelCounterSlave)
self.setLayout(layout)
self.show()
def setupConn(self):
self.thread = QThread()
slave = Slave()
timer = QTimer()
timer.setInterval(100)
# Make sure that both objects are removed properly once the thread is terminated
self.thread.finished.connect(timer.deleteLater)
self.thread.finished.connect(slave.deleteLater)
# Connect the button to the toggle slot of this widget
self.buttonToggleSlave.clicked.connect(self.toggle)
# Connect widget's toggle signal (emitted from inside widget's toggle slot) to slave's toggle slot
self.toggleSignal.connect(slave.toggle)
# Connect timer's timeout signal to slave's work slot
timer.timeout.connect(slave.work)
timer.timeout.connect(self.timeout)
# Connect slave's countSignal signal to widget's viewCounter slot
slave.countSignal.connect(self.viewCounter)
# Start timer
timer.start()
# Move timer and slave to thread
timer.moveToThread(self.thread)
slave.moveToThread(self.thread)
# Start thread
self.thread.start()
#pyqtSlot(int)
def viewCounter(self, value):
print(value)
self.labelCounterSlave.setText(str(value))
#pyqtSlot()
def toggle(self):
print("Toggle called")
self.buttonToggleSlave.setText("Halt" if (self.buttonToggleSlave.text() == "Start") else "Start")
self.toggleSignal.emit()
#pyqtSlot()
def timeout(self):
print("Tick")
if __name__ == "__main__":
app = QApplication([])
w = Master()
w.setStyleSheet('cleanlooks')
app.exec_()
Following things are not triggered/emitted:
timeout() slot of my widget - I added this to see why the timer is not triggering my worker's slot but all I found out is that it doesn't work here either...
work() and toggle() slots inside my Slave worker class
countSignal - it is never emitted since my widget's viewCounter() slot is never triggered
I have no idea what I'm doing wrong. I have connected the signals and slots, started my timer, moved it along with the worker to my separate thread and started the thread.
Am I missing something here?

There are several issues with the code that are preventing it from working correctly.
As per the documentation, you must start (and stop) a timer from the thread it resides in. You cannot start it from another thread. If you want to have the timer reside in the thread, you should relocate the instantiation code to the Slave object and call timer.start() in a slot connected to the threads started signal. You do need to be careful here though as the Slave.__init__ method is going to still run in the main thread. Alternatively, you could just leave the timer in the main thread.
slave and timer are being garbage collected when setupConn() has finished. Store them as self.slave and self.timer. (Alternatively you should be able to specify a parent for them, but this seems to result in app crashes on exit so it's probably best to stick to storing them as instance attributes).
I assume the line self.counter += self.counter should really be self.counter += 1? Otherwise the counter is never incremented :)

Related

PyQT worker does not emit finished signal when stopped in between execution [duplicate]

This question already has answers here:
Background thread with QThread in PyQt
(7 answers)
Example of the right way to use QThread in PyQt?
(3 answers)
PyQt5: start, stop and pause a thread with progress updates
(1 answer)
Closed 3 days ago.
Save New Duplicate & Edit Just Text Twitter
# importing libraries
from PyQt6.QtWidgets import *
from PyQt6.QtGui import *
from PyQt6.QtCore import *
import sys
import time
class Worker(QObject):
finished = pyqtSignal()
_stop = False
def run(self):
i=0
print('Started')
for i in range(5):
if self._stop:
print('Breaking')
break
time.sleep(1)
i+=1
self.finished.emit()
print('Done')
def stop(self):
self._stop = True
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
try:
self.worker.stop()
self.thread.wait()
except (RuntimeError,AttributeError):
pass
print('Here')
self.createThread()
self.thread.started.connect(self.worker.run)
self.worker.quit = False
self.thread.finished.connect(lambda:print('Stopped'))
self.thread.start()
self.btn = QPushButton(self)
self.btn.move(40, 80)
self.btn.setText('Stop')
self.btn.clicked.connect(self.initUI)
self.setWindowTitle("Python")
self.show()
def createThread(self):
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
if __name__ == '__main__':
App = QApplication(sys.argv)
window = Example()
sys.exit(App.exec())
I wrote an MVCE version of a problem I have been having.
The initUI function creates a window with a button in it. It also creates a thread that waits for 5 loops with a 1 second sleep each loop. When I click the button, it calls initUI again. This time, if the thread is still running, it calls the worker.stop function to stop the thread in between. When I do this, although the worker.run function finishes execution, the worker.finished signal is not emitted. However, the signal is emitted when the loop finishes on its own without pressing the button (i.e. waiting 5 seconds). Any explanation?

Pyqt threaded processes application crashing on second launch

I made a small test application to practice threading - on the first launch it works fine, with the loop printing to the console while the gui is still running. However, the kernel crashes and restarts if I close the app and then open it again afterwards.
I've had this issue before with pyqt5 applications, and it's usually solved when I add the QCoreApplication.instance() section at the end of my script. I was wondering whether this time there was an error with the way I implemented my threading.
import sys
import time
from PyQt5 import QtWidgets
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtCore import QCoreApplication
class Worker(QObject):
finished = pyqtSignal()
def __init__(self):
super(Worker, self).__init__()
self.working = True
def work(self):
while self.working:
# do stuff here
print("I'm running")
time.sleep(1)
self.finished.emit() # alert our gui that the loop stopped
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(50, 50, 200, 150)
self.setWindowTitle("Program")
self.startbtn = QtWidgets.QPushButton("Start", self)
self.startbtn.resize(self.startbtn.minimumSizeHint())
self.startbtn.move(50, 50)
self.stopbtn = QtWidgets.QPushButton("Stop", self)
self.stopbtn.move(50, 100)
self.stopbtn.resize(self.stopbtn.minimumSizeHint())
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.startbtn.clicked.connect(self.thread.start) # start the thread when we click the start button
self.thread.started.connect(self.worker.work) # begin our worker object's loop
self.stopbtn.clicked.connect(self.stop_loop) # stop the loop on the stop button click
self.worker.finished.connect(self.loop_finished) # do something in the gui when the worker loop ends
def stop_loop(self):
self.worker.working = False
def loop_finished(self):
print('Looped Finished')
if __name__ == "__main__":
app = QCoreApplication.instance() # Fixes error with kernel crashing on second run of application
if app is None: # PyQt doesn't like multiple QApplications in the same process, therefore
app = QtWidgets.QApplication(sys.argv) # apart from the initial run, the first QApplication instance will run
window = Window()
window.show()
app.exec_()

Pyqt5 Two QThread communicate use signal and slot issue

I'm trying to use the pyqt qthread for multithreading program.
In the code, there are two different workers instance. I try to use signal and slot for data share.
but it seemed that the signal is blocked until one of the qthread is finished.
this is the code.
from PyQt5.QtCore import QCoreApplication,QThread, QObject, pyqtSignal, pyqtSlot
import time
import sys
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int)
#pyqtSlot()
def work(self): # A slot takes no params
for i in range(1, 10):
time.sleep(1)
self.intReady.emit(i)
self.finished.emit()
class Worker2(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int)
#pyqtSlot()
def work(self): # A slot takes no params
for i in range(1, 10):
time.sleep(1)
self.intReady.emit(i)
self.finished.emit()
#pyqtSlot(int)
def revsignal(self, val):
print("hello rev a signal"+str(val))
def updateLabel(val):
print("updateLable "+str(val))
app = QCoreApplication(sys.argv)
# 1 - create Worker and Thread inside the Form
worker = Worker() # no parent!
thread = QThread() # no parent!
worker2 = Worker2()
thread2 = QThread()
worker.intReady.connect(updateLabel)
worker.intReady.connect(worker2.revsignal)
worker.moveToThread(thread)
worker.finished.connect(thread.quit)
thread.started.connect(worker.work)
# self.thread.finished.connect(app.exit)
worker2.moveToThread(thread2)
worker2.finished.connect(thread2.quit)
thread2.started.connect(worker2.work)
thread2.finished.connect(app.exit)
thread2.start()
print("after thread2 start")
thread.start()
print("after thread1 start.")
sys.exit(app.exec_())
and the output is here
after thread2 start
after thread1 start.
updateLable 1
updateLable 2
updateLable 3
updateLable 4
updateLable 5
updateLable 6
updateLable 7
updateLable 8
updateLable 9
hello rev a signal1
hello rev a signal2
hello rev a signal3
hello rev a signal4
hello rev a signal5
hello rev a signal6
hello rev a signal7
hello rev a signal8
hello rev a signal9
Process finished with exit code 0
i don't know why the worker2 thread can't receive the signal as soon as the work1 emit the signal. until the work1 thread ended.
Any help is greatly appreciated!
First thing is that all slots should be members of a class which inherits from QObject. So make a class like this
class Manager(QObject):
#pyqtSlot(int)
def updateLabel(self, val):
print("updateLable "+str(val))
manager = Manager()
Next, as per the docs here, there is an additional argument which can be passed to connect to control the behavior of cross-thread signal processing. You need to change three lines.
from PyQt5.QtCore import QCoreApplication, QThread, QObject, pyqtSignal, pyqtSlot, Qt
to get the Qt namespace, and both of
worker.intReady.connect(manager.updateLabel, type=Qt.DirectConnection)
worker.intReady.connect(worker2.revsignal, type=Qt.DirectConnection)
to set the connection type.
You're misunderstanding how threads and signals/slots work.
Signal and Slot handling
When a signal is emitted, slots that it's connected to should be run. When using a DirectConnection, that means directly calling the slot function, but that's only the default when objects live on the same thread. When objects live on different threads (or you've used a QueuedConnection), the signal enqueues an event to run the slot in the appropriate thread's event queue. This is what you want.
Thread event loops
Each thread is a separate thread of executing, running an event loop. The event loop calls the slots that have received a signal, but it does so sequentially, that is, it calls one slot, then when that function returns, it calls the next, and so on.
What you're doing wrong
It's a combination of 2 things.
First, you're doing your entire logic loop in the slot, meaning no further events will be handled until your loop is finished.
Second, you're calling time.sleep in the slot, which causes that entire thread to sleep for 1 second.
Combined, this means that both threads will first call the work slot, be 'busy' for 10 seconds until that slot is done, and then do the rest of the work.
Solutions
Direct Connection
You can use a direct connection, as suggested by buck54321, but this just means that the work function of worker1 (running on thread1) will call worker2.revsignal directly, still on thread1.
Manually process events
You can call QCoreApplication.processEvents() to run (an iteration of) the event loop by hand. Sometimes this can be the right solution, but it's messy,
and you're still going to cause the thread to sleep for a second at a time.
Use timed events
Instead of looping, you can use timers to periodically call your slots and do work then. This means you don't have to loop until you're done. I've included an example using QTimer.singleShot.
Code
Cleaned up
Here is a cleaned up version that still behaves roughly the same:
from PyQt5.QtCore import QCoreApplication,QThread, QObject, pyqtSignal, pyqtSlot
import time
import sys
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int, int)
def __init__(self, number):
super().__init__()
self.number = number
#pyqtSlot()
def work(self):
for i in range(1, 10):
time.sleep(1)
self.intReady.emit(self.number, i)
self.finished.emit()
#pyqtSlot(int)
def revsignal(self, val):
print("hello rev a signal"+str(val))
class Label(QObject):
#pyqtSlot(int, int)
def updateLabel(self, source, val):
print(f"updateLabel ({source}): {val}")
class Exiter(QObject):
done = pyqtSignal()
def __init__(self, num_threads):
super().__init__()
self.live_threads = num_threads
#pyqtSlot()
def finished(self):
self.live_threads -= 1
if self.live_threads <= 0:
self.done.emit()
app = QCoreApplication(sys.argv)
# 1 - create Worker and Thread inside the Form
worker1 = Worker(1) # no parent!
thread1 = QThread() # no parent!
worker2 = Worker(2)
thread2 = QThread()
label = Label()
exiter = Exiter(2) # Waiting for 2 threads
worker1.intReady.connect(label.updateLabel)
worker1.intReady.connect(worker2.revsignal)
worker1.moveToThread(thread1)
worker1.finished.connect(thread1.quit)
thread1.started.connect(worker1.work)
thread1.finished.connect(exiter.finished)
worker2.intReady.connect(label.updateLabel)
worker2.moveToThread(thread2)
worker2.finished.connect(thread2.quit)
thread2.started.connect(worker2.work)
thread2.finished.connect(exiter.finished)
exiter.done.connect(app.exit)
thread2.start()
print("after thread2 start")
thread1.start()
print("after thread1 start.")
sys.exit(app.exec_())
Hack
This version forces event processing before sleeping, which at least handles other events, but still causes the thread to sleep.
from PyQt5.QtCore import QCoreApplication,QThread, QObject, pyqtSignal, pyqtSlot
import time
import sys
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int, int)
def __init__(self, number):
super().__init__()
self.number = number
#pyqtSlot()
def work(self):
for i in range(1, 10):
QCoreApplication.processEvents()
time.sleep(1)
self.intReady.emit(self.number, i)
self.finished.emit()
#pyqtSlot(int)
def revsignal(self, val):
print("hello rev a signal"+str(val))
class Label(QObject):
#pyqtSlot(int, int)
def updateLabel(self, source, val):
print(f"updateLabel ({source}): {val}")
class Exiter(QObject):
done = pyqtSignal()
def __init__(self, num_threads):
super().__init__()
self.live_threads = num_threads
#pyqtSlot()
def finished(self):
self.live_threads -= 1
if self.live_threads <= 0:
self.done.emit()
app = QCoreApplication(sys.argv)
# 1 - create Worker and Thread inside the Form
worker1 = Worker(1) # no parent!
thread1 = QThread() # no parent!
worker2 = Worker(2)
thread2 = QThread()
label = Label()
exiter = Exiter(2) # Waiting for 2 threads
worker1.intReady.connect(label.updateLabel)
worker1.intReady.connect(worker2.revsignal)
worker1.moveToThread(thread1)
worker1.finished.connect(thread1.quit)
thread1.started.connect(worker1.work)
thread1.finished.connect(exiter.finished)
worker2.intReady.connect(label.updateLabel)
worker2.moveToThread(thread2)
worker2.finished.connect(thread2.quit)
thread2.started.connect(worker2.work)
thread2.finished.connect(exiter.finished)
exiter.done.connect(app.exit)
thread2.start()
print("after thread2 start")
thread1.start()
print("after thread1 start.")
sys.exit(app.exec_())
Timed events
Here the logic has been rewritten to use a timed event, instead of sleeping.
from PyQt5.QtCore import QCoreApplication, QThread, QTimer, QObject, pyqtSignal, pyqtSlot
import sys
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int, int)
def __init__(self, number):
super().__init__()
self.number = number
self.to_emit = iter(range(1, 10))
#pyqtSlot()
def work(self):
try:
value = next(self.to_emit)
self.intReady.emit(self.number, value)
QTimer.singleShot(1000, self.work)
except StopIteration:
self.finished.emit()
#pyqtSlot(int)
def revsignal(self, val):
print("hello rev a signal"+str(val))
class Label(QObject):
#pyqtSlot(int, int)
def updateLabel(self, source, val):
print(f"updateLabel ({source}): {val}")
class Exiter(QObject):
done = pyqtSignal()
def __init__(self, num_threads):
super().__init__()
self.live_threads = num_threads
#pyqtSlot()
def finished(self):
self.live_threads -= 1
if self.live_threads <= 0:
self.done.emit()
app = QCoreApplication(sys.argv)
# 1 - create Worker and Thread inside the Form
worker1 = Worker(1) # no parent!
thread1 = QThread() # no parent!
worker2 = Worker(2)
thread2 = QThread()
label = Label()
exiter = Exiter(2) # Waiting for 2 threads
worker1.intReady.connect(label.updateLabel)
worker1.intReady.connect(worker2.revsignal)
worker1.moveToThread(thread1)
worker1.finished.connect(thread1.quit)
thread1.started.connect(worker1.work)
thread1.finished.connect(exiter.finished)
worker2.intReady.connect(label.updateLabel)
worker2.moveToThread(thread2)
worker2.finished.connect(thread2.quit)
thread2.started.connect(worker2.work)
thread2.finished.connect(exiter.finished)
exiter.done.connect(app.exit)
thread2.start()
print("after thread2 start")
thread1.start()
print("after thread1 start.")
sys.exit(app.exec_())

PyQt, QThread: GUI freeze with large amount of SIGNAL from another thread (QListWidget massive update)

I have an app that starts the GUI and performs "the heavy part" of the code in another thread using QThread. In this thread I emit a SIGNAL that is connected to the GUI Class and that performs addItem on QListWidget.
There are a massive "signaling" from this thread to the GUI and it "freeze".
Is there a way to avoid this? Have I to use another mini GUI in different thread only for QListWidget?
Thanks
EDIT:
This is the thread that execute the heavy logic
class YourThreadName(QThread):
def __init__(self, some variables):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
# Here there is a for cycle that emits a SIGNAL
for ... :
...
self.emit(SIGNAL("needed_variable"), needed_variable)
...
In the GUI Class there are some methods, particularly:
class GUI(QtGui.QMainWindow, GUI.Ui_MainWindow):
def __init__(self, parent=None):
super(GUI, self).__init__(parent)
self.setupUi(self)
def ... (self):
...
def start_main_code(self):
self.new_thread = YourThreadName(some variables)
self.connect(self.new_thread, SIGNAL("finished()"), self.done)
self.connect(self.new_thread, SIGNAL("needed_variable"), self.show_variable)
self.new_thread.start()
def show_variable(self, data):
self.QListWidget_object.addItem(data)
def ... (self):
...
The script below is a Minimal, Complete, and Verifiable Example based on the information currently given in your question and comments. It emits data from a worker thread every 10ms and updates a list-widget in the GUI. On my Linux system (using Python-3.6.3, Qt-4.8.7 and PyQt-4.12.1) it does not block or freeze the GUI. There is obviously some flickering whilst the list-widget is being updated, but I am able to select items, scroll up and down, click the button, etc. And if I increase the sleep to 25ms, I don't even get any flickering.
UPDATE:
The performance can be improved by using setUniformItemSizes and sending the messages in batches. On my system, after a slight initial delay, the list populates with fifty thousand items almost instantly.
import sys
from PyQt4 import QtCore, QtGui
class Worker(QtCore.QThread):
message = QtCore.pyqtSignal(object)
def run(self):
batch = []
for index in range(50000):
if len(batch) < 200:
batch.append(index)
continue
self.message.emit(batch)
batch = []
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.listWidget = QtGui.QListWidget()
self.listWidget.setUniformItemSizes(True)
self.button = QtGui.QPushButton('Start')
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.listWidget)
layout.addWidget(self.button)
self.worker = Worker()
self.worker.message.connect(self.handleMessages)
def handleMessages(self, batch):
for message in batch:
self.listWidget.addItem('Item (%s)' % message)
def handleButton(self):
if not self.worker.isRunning():
self.worker.start()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 50, 200, 400)
window.show()
sys.exit(app.exec_())

PyQt: How to deal with QThread?

In the following code I try to deal with QThread. In this executable example there are three buttons: first for start, second for stop and third for close. Well, when I start the task its runs like a charm. BUT when I want the while-loop to stop I click on the stop-button. And now, there is a problem: the while-loop doesn't stop.
You see, the stop-button emits a signal to call the stop() method on TestTask().
What is wrong?
from sys import argv
from PyQt4.QtCore import QObject, pyqtSignal, QThread, Qt, QMutex
from PyQt4.QtGui import QDialog, QApplication, QPushButton, \
QLineEdit, QFormLayout, QTextEdit
class TestTask(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self._mutex = QMutex()
self._end_loop = True
def init_object(self):
while self._end_loop:
print "Sratus", self._end_loop
def stop(self):
self._mutex.lock()
self._end_loop = False
self._mutex.unlock()
class Form(QDialog):
stop_loop = pyqtSignal()
def __init__(self, parent=None):
QDialog.__init__(self, parent)
self.init_ui()
def init_ui(self):
self.pushButton_start_loop = QPushButton()
self.pushButton_start_loop.setText("Start Loop")
self.pushButton_stop_loop = QPushButton()
self.pushButton_stop_loop.setText("Stop Loop")
self.pushButton_close = QPushButton()
self.pushButton_close.setText("Close")
layout = QFormLayout()
layout.addWidget(self.pushButton_start_loop)
layout.addWidget(self.pushButton_stop_loop)
layout.addWidget(self.pushButton_close)
self.setLayout(layout)
self.setWindowTitle("Tes Window")
self.init_signal_slot_pushButton()
def start_task(self):
self.task_thread = QThread(self)
self.task_thread.work = TestTask()
self.task_thread.work.moveToThread(self.task_thread)
self.task_thread.started.connect(self.task_thread.work.init_object)
self.stop_loop.connect(self.task_thread.work.stop)
self.task_thread.start()
def stop_looping(self):
self.stop_loop.emit()
def init_signal_slot_pushButton(self):
self.pushButton_start_loop.clicked.connect(self.start_task)
self.pushButton_stop_loop.clicked.connect(self.stop_looping)
self.pushButton_close.clicked.connect(self.close)
app = QApplication(argv)
form = Form()
form.show()
app.exec_()
The stop_loop signal is converted to an event and sent to the thread of the signal receiver. But your worker object is running a blocking while-loop, and this prevents the thread processing any pending events in its event-queue. So the slot connected to the stop_loop signal will never be called.
To work around this, you can call processEvents in the while-loop to allow the thread to process its pending events:
def init_object(self):
while self._end_loop:
QThread.sleep(1)
QApplication.processEvents()
print "Status", self._end_loop
Alternatively, you could call the worker's stop() method directly instead of emitting a signal. Strictly speaking, this is not thread-safe, but it would only be a problem if multiple threads could call stop() at the same time. So a more correct way to do that is to use a mutex to protect the value being changed:
class TestTask(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self._mutex = QtCore.QMutex()
self._end_loop = True
def stop(self):
self._mutex.lock()
self._end_loop = False
self._mutex.unlock()
And now calling stop() directly from the main thread is thread-safe.

Resources