Pyqt5 Two QThread communicate use signal and slot issue - pyqt

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

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?

Is there a way to cancel a background worker task in PyQt5 using a pushButton?

I'm trying to make the background worker start with the start button and stop with the stop button. When I push start the program starts but I can't stop it and it also exits the gui when completed. Sorry, I am a novice just trying to learn. I found this example here and I am just trying to expand on it.
Background thread with QThread in PyQt
# main.py
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout, QPushButton
import sys
import worker
class Form(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("0") # start out at 0.
##### I added the stuff below 6/8/2020 ####################################
k = 2
self.button1 = QPushButton("Push to start") # create start button
self.button2 = QPushButton("Push to Stop") # create a stop button.
self.button1.clicked.connect(self.start) # connect button to function
self.button2.clicked.connect(self.stop) # connect button to function
########################################################################
# 1 - create Worker and Thread inside the Form
self.obj = worker.Worker() # no parent! #### Calls to worker.py
self.thread = QThread() # no parent! #### Creates an empty thread
# 2 - Connect Worker`s Signals to Form method slots to post data.
self.obj.intReady.connect(self.onIntReady) # connects the definition below
# 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(k)) #### starts the counter in worker.py
# 6 - Start the thread
self.thread.start()
# 7 - Start the form
self.initUI()
def start(self):
k=2
self.obj.procCounter(k)
def stop(self):
self.thread.quit()
def initUI(self):
grid = QGridLayout()
self.setLayout(grid)
grid.addWidget(self.label,0,1)
grid.addWidget(self.button1, 0, 0)
grid.addWidget(self.button2, 0, 2)
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_())
# worker.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time
class Worker(QObject): # this is called by Worker.worker() in main.py
finished = pyqtSignal() # sets up to emit a signal back to main.py
intReady = pyqtSignal(int) # emits an integer to main.py def onIntReady
#pyqtSlot()
def procCounter(self,k): # A slot takes no params
for i in range(1, 10):
time.sleep(1)
self.intReady.emit(i*k) # emits the signal to onIntReady
#def procCounter(self): # A slot takes no params
# for i in range(1, 100):
# time.sleep(1)
# self.intReady.emit(i)
self.finished.emit()

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.

PyQt signals between threads not emitted

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

How to write a multi-threaded Pyside application

What is the simplest way to multi-thread a Pyside application, so the GUI can be operational and the thread will still run?
Thread class:
class MyLongThread(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def run(self):
while 1:
self.msleep(100)
print("run")
Complete .pyw
import sys,time
from PySide.QtGui import *
from PySide.QtCore import *
from PySide.QtWebKit import *
def thread():
global threade
threade = MyLongThread()
threade.run()
def thread_terminate():
global threade
threade.terminate()
class MyLongThread(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def run(self):
while 1:
self.msleep(100)
print("run")
app = QApplication(sys.argv)
wid = QWidget()
wid.resize(250, 400)
wid.setWindowTitle('Threaded Program')
#wid.setWindowIcon(QIcon('web.png'))
#### BUTTONS
btn = QPushButton('Stop', wid)
btn.setToolTip('Stop the thread.') ## Stop the thread
btn.resize(btn.sizeHint())
btn.move(147, 50)
btn.clicked.connect(thread_terminate)
qbtn = QPushButton('Start', wid)
qbtn.setToolTip('Start the thread.') ## End the Thread
qbtn.resize(btn.sizeHint())
qbtn.move(27, 50)
qbtn.clicked.connect(thread)
####
#### LABEL
label = QLabel('Start The Thread :)',wid)
label.resize(label.sizeHint())
label.move(28, 15)
####
wid.show()
sys.exit(app.exec_())
When I run the code and press the start button, it freezes the gui but prints run.
Don't directly call thread.run() as this executes the method in the main thread. Instead, call thread.start() which will launch a thread and start executing your run() method in the thread.

Resources