QThread.finished signal isn't emitted after worker finishes - python

I have the following code:
import time
from PyQt5.QtCore import QThread, QObject
from PyQt5.QtWidgets import QWidget, QApplication
class Worker(QObject):
def run(self):
time.sleep(1)
print("Worker is finished")
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.show()
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.thread.finished.connect(self.on_thread_finished)
self.thread.start()
def on_thread_finished(self):
print("Thread finished")
self.thread.deleteLater()
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
app.exec_()
Run it, it shows the window, prints Worker is finished, nothing more. That's weird. IMHO when the worker is finished, the thread should be finished too, which means the on_thread_finished method should be called and Thread finished should be printed. But it wasn't. Why?

When you use moveToThread instead of reimplementing QThread.run, the thread will start its own event-loop which will wait until exit/quit is explicitly called. The finished signal is only emitted after the event-loop has stopped (and/or once run returns). The usual way to handle this scenario is to emit a custom signal from the worker, and connect it to the thread's quit slot. (NB: cross-thread signal-slot connections are guaranteed to be thread-safe).
So your example will work as expected with the following changes:
class Worker(QObject):
finished = QtCore.pyqtSignal()
def run(self):
time.sleep(1)
print("Worker is finished")
self.finished.emit()
class MainWindow(QWidget):
def __init__(self):
...
self.worker.moveToThread(self.thread)
self.worker.finished.connect(self.thread.quit)

when the worker is finished, the thread should be finished too
That's not how it works. Your Worker::run method is being invoked as a slot via the usual signal/slot mechanism after which the QThread will continue to process events as normal.
If you want to terminate the QThread when Worker::run has completed you need to tell it to do so explicitly...
import time
from PyQt5.QtCore import Qt, QThread, QObject
from PyQt5.QtWidgets import QWidget, QApplication
class Worker(QObject):
# Constructor accepts the QThread as a parameter and stashes it
# for later use.
def __init__(self, thread):
super(Worker, self).__init__()
self.m_thread = thread
def run(self):
time.sleep(1)
print("Worker is finished")
# We're done so ask the `QThread` to terminate.
if self.m_thread:
self.m_thread.quit()
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.show()
self.thread = QThread()
# Pass the QThread to the Worker's ctor.
self.worker = Worker(self.thread)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.thread.finished.connect(self.on_thread_finished)
self.thread.start()
def on_thread_finished(self):
print("Thread finished")
self.thread.deleteLater()
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
app.exec_()
Very 'inelegant' but it conveys the idea.
A simpler alternative that springs to mind would be to simply make use of QThread::currentThread(), in which case your Worker::run method becomes...
class Worker(QObject):
def run(self):
time.sleep(1)
print("Worker is finished")
# We're done so ask the `QThread` to terminate.
QThread.currentThread().quit()

Related

Reuse QThread for different workers in python

I want to use the same QThread for different workers. That is, first worker1 is passed to the thread and runs. After it finishes, worker2 should be passed to the thread and runs.
So, this is my attempt
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QAction
from PyQt5 import uic
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from time import sleep
class MainWin(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.thread = QThread()
self.worker1 = Worker1()
self.worker1.finished.connect(self.w1_finish)
self.thread.started.connect(self.worker.run)
self.thread.start()
def w1_finish():
self.thread.quit()
self.worker1.deleteLater()
self.worker2 = Worker2()
self.worker2.finished.connect(self.w2_finish)
self.thread.started.connect(self.worker2.run)
self.thread.start()
def w2_finish():
print("all done")
class Worker1(QObject):
finished = pyqtSignal()
def run():
for i in range(5):
print(f"This is worker1: {i}")
sleep(1)
self.finished.emit()
class Worker2(QObject):
finished = pyqtSignal()
def run():
for i in range(5):
print(f"This is worker2: {i}")
sleep(1)
self.finished.emit()
Strangely, this sometimes works as intended. I mean, I first get the outputs from worker 1 and then from worker2. However, in some cases, worker 2 seems to be executed after worker 1 has finished. Is there a problem? Should I use two separate thread instances for the task?

PyQt5 terminate application from worker

This is an example of the application that starts the QApp -> Worker -> Something that causes sys.exit() inside the worker
import sys
from PyQt5.QtCore import QRunnable, pyqtSlot, QThreadPool
from PyQt5.QtWidgets import QWidget, QApplication
class App(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
self.threadpool = QThreadPool()
worker = Worker()
self.threadpool.start(worker)
def init_ui(self):
self.setFixedSize(600, 300)
self.show()
class Worker(QRunnable):
def __init__(self):
super(Worker, self).__init__()
#pyqtSlot()
def run(self):
print("Running worker...")
run_application()
def run_window():
app = QApplication([])
ex = App()
sys.exit(app.exec_())
def run_application():
print('Running application...')
sys.exit(10)
if __name__ == '__main__':
run_window()
There are several cases when I run it:
exits with code 0
exits with code 10
window keeps hanging
It feels like a racing.
So what is the correct way to terminate the window in case of the sys.exit() or the exception inside the worker?
Edit: forgot to mention that I need to return the exit code and track it since I run the window through the subprocess/Popen.
If you want to close the windows then a possible solution is to terminate the eventloop using QCoreApplication::quit():
def run_application():
print("Running application...")
QCoreApplication.quit()
Or QCoreApplication::exit():
def run_application():
print("Running application...")
QCoreApplication.exit(10)

QThread stops event processing

I would like to start a Thread with PyQt, that performs some actions independently of the rest of the app. This includes running an external program. This may take some minutes.
My problem is that using QThread for this stops the whole app.
When running this small program, clicking the button will freeze the app for 10 seconds.
What can I do to make QThread behave as a Thread. I know that I can add some timers and divide it into events, but that is not my idea of a thread.
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from PyQt5.QtCore import QThread
class ProgramThread(QThread):
def __init__(self):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
QThread.sleep(10)
class App(QWidget):
def __init__(self):
super().__init__()
thread = QPushButton('Start Thread',self)
thread.clicked.connect(self.startthread)
thread.move(20,100)
self.show()
def startthread(self):
t = ProgramThread()
t.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
QThread is not a thread, it is a thread handler. In your case the problem is that the variable t is local so it will be deleted after executing the start, at that moment the __del__ method is called, and this calls wait() that runs in the main thread blocking the GUI, this blocking It will be until the run method finishes executing, so in conclusion the problem is that t is a local variable, the solution is to keep it in time so there are 2 possibilities: 1) pass it to self as a parent or 2) make it a member of the class:
class ProgramThread(QThread):
def run(self):
QThread.sleep(10)
def __del__(self):
self.wait()
class App(QWidget):
def __init__(self):
super().__init__()
thread = QPushButton('Start Thread',self)
thread.clicked.connect(self.startthread)
thread.move(20,100)
self.show()
def startthread(self):
# method 1
t = ProgramThread(self)
t.start()
# method 2
# self.t = ProgramThread()
# self.t.start()

Is it possible to use a ThreadPoolExecutor with a QThread? Seems to create dummy thread that doesn't terminate

I'm making an application in PyQt5 that runs a variety of long running tasks, such as scraping web pages. In order to avoid crashing the GUI, I've been using QThreads and QObjects
Currently I have a class that inherits QObject and contains a method for scraping the web pages. I move that object on to a QThread, connect a finished signal with a method that quits the thread and then start the thread.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import threading
from concurrent.futures import ThreadPoolExecutor
import requests
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.init_ui()
self.test_obj = None
self.thread = QThread(self)
def init_ui(self):
widget = QWidget()
layout = QHBoxLayout()
widget.setLayout(layout)
thread_button = QPushButton("Start Thread")
check_button = QPushButton("Check Thread")
layout.addWidget(thread_button)
layout.addWidget(check_button)
thread_button.clicked.connect(self.work)
check_button.clicked.connect(self.check)
self.setCentralWidget(widget)
self.show()
def work(self):
self.test_obj = TestObject()
self.test_obj.moveToThread(self.thread)
self.test_obj.finished.connect(self.finished)
self.thread.started.connect(self.test_obj.test)
self.thread.start()
def check(self):
for t in threading.enumerate():
print(t.name)
#pyqtSlot()
def finished(self):
self.thread.quit()
self.thread.wait()
print("Finished")
class TestObject(QObject):
finished = pyqtSignal()
def __init__(self):
super(TestObject, self).__init__()
def test(self):
with ThreadPoolExecutor() as executor:
executor.submit(self.get_url)
self.finished.emit()
def get_url(self):
res = requests.get("http://www.google.com/")
print(res)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Main()
sys.exit(app.exec_())
Everything works as expected, I get a:
"Response [200]"
"Finished"
Printed in the console. However when I check the running threads, it shows dummy-1 thread is still running. Every time I run, it creates an additional dummy thread. Eventually I am unable to create any more threads and my application crashes.
Is it possible to use a ThreadPoolExecutor on a QThread like this? If so, is there a correct way to ensure that I don't have these dummy threads still running once I've finished my task?

PyQt4 Wait in thread for user input from GUI

I have a thread class "MyThread" and my main application which is simply called "Gui". I want to create a few objects from the thread class but for this example I created only one object. The thread class does some work, then emits a signal to the Gui class, indicating that a user input is needed (this indication for now is simply changing the text of a button). Then the thread should wait for a user input (in this case a button click) and then continue doing what it is doing...
from PyQt4 import QtGui, QtCore
class MyTrhead(QtCore.QThread):
trigger = QtCore.pyqtSignal(str)
def run(self):
print(self.currentThreadId())
for i in range(0,10):
print("working ")
self.trigger.emit("3 + {} = ?".format(i))
#### WAIT FOR RESULT
time.sleep(1)
class Gui(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Gui, self).__init__(parent)
self.setupUi(self)
self.pushButton.clicked.connect(self.btn)
self.t1 = MyTrhead()
self.t1.trigger.connect(self.dispaly_message)
self.t1.start()
print("thread: {}".format(self.t1.isRunning()))
#QtCore.pyqtSlot(str)
def dispaly_message(self, mystr):
self.pushButton.setText(mystr)
def btn(self):
print("Return result to corresponding thread")
if "__main__" == __name__:
import sys
app = QtGui.QApplication(sys.argv)
m = Gui()
m.show()
sys.exit(app.exec_())
How can I wait in (multiple) threads for a user input?
By default, a QThread has an event loop that can process signals and slots. In your current implementation, you have unfortunately removed this behaviour by overriding QThread.run. If you restore it, you can get the behaviour you desire.
So if you can't override QThread.run(), how do you do threading in Qt? An alternative approach to threading is to put your code in a subclass of QObject and move that object to a standard QThread instance. You can then connect signals and slots together between the main thread and the QThread to communicate in both directions. This will allow you to implement your desired behaviour.
In the example below, I've started a worker thread which prints to the terminal, waits 2 seconds, prints again and then waits for user input. When the button is clicked, a second separate function in the worker thread runs, and prints to the terminal in the same pattern as the first time. Please note the order in which I use moveToThread() and connect the signals (as per this).
Code:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import time
class MyWorker(QObject):
wait_for_input = pyqtSignal()
done = pyqtSignal()
#pyqtSlot()
def firstWork(self):
print 'doing first work'
time.sleep(2)
print 'first work done'
self.wait_for_input.emit()
#pyqtSlot()
def secondWork(self):
print 'doing second work'
time.sleep(2)
print 'second work done'
self.done.emit()
class Window(QWidget):
def __init__(self, parent = None):
super(Window, self).__init__()
self.initUi()
self.setupThread()
def initUi(self):
layout = QVBoxLayout()
self.button = QPushButton('User input')
self.button.setEnabled(False)
layout.addWidget(self.button)
self.setLayout(layout)
self.show()
#pyqtSlot()
def enableButton(self):
self.button.setEnabled(True)
#pyqtSlot()
def done(self):
self.button.setEnabled(False)
def setupThread(self):
self.thread = QThread()
self.worker = MyWorker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.firstWork)
self.button.clicked.connect(self.worker.secondWork)
self.worker.wait_for_input.connect(self.enableButton)
self.worker.done.connect(self.done)
# Start thread
self.thread.start()
if __name__ == "__main__":
app = QApplication([])
w = Window()
app.exec_()

Categories