So I have a program which a runs a Qt GUI. I don't want to post code of my program but the code I'm showing is applicable to mine. So I got my file with a new thread.
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
print("Starting Thread")
time.sleep(5)
some_method()
some_method2()
print("Closing Thread")
And i got my main.py
from threadFile import MyThread
t1 = MyThread()
MyThread.start()
self.some_other_method()
I want that some_other_method() to run after the t1 Thread is done. I can't use .join() because it freezes the UI and I cant include some_other_method() in the threadFile because some_other_method() is an instance method in my main.py and importing the class in my threadFile would produce a circular import. I hope my problem is clear.
Then create a QObject that emits the finished signal when the tasks are finished executing, and through that signal invoke the function you want:
import threading
import time
from PyQt5 import QtCore, QtWidgets
class Signaller(QtCore.QObject):
started = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal()
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.signaller = Signaller()
def run(self):
self.signaller.started.emit()
print("Starting Thread")
time.sleep(5)
print("Closing Thread")
self.signaller.finished.emit()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.button = QtWidgets.QPushButton("Press me")
self.setCentralWidget(self.button)
self.button.clicked.connect(self.on_clicked)
#QtCore.pyqtSlot()
def on_clicked(self):
self.button.setEnabled(False)
t1 = MyThread()
t1.signaller.finished.connect(self.on_finished)
t1.start()
#QtCore.pyqtSlot()
def on_finished(self):
self.some_other_method()
self.button.setEnabled(True)
def some_other_method(self):
print("test")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Related
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()
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)
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?
I would like to have 2 worker threads in my application. One should start running as soon as the GUI is loaded and another one should start later by some signal. Let's say it's a button click.
I came across a weird behavior when my Python interpreter crashes(as in shows me Windows error "Python stopped working", no stack trace) on executing the second thread.
Here is an example, that will crash right after I click the button.
class Worker(QtCore.QThread):
def __init__(self, method_to_run):
super().__init__()
self.method = method_to_run
def run(self):
self.method()
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QPushButton('Test', self)
self.label = QLabel(self)
self.button.clicked.connect(self.handleButton)
layout = QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.button)
self.worker = Worker(self.test_method)
self.worker.start()
def handleButton(self):
self.label.setText('Button Clicked!')
worker = Worker(self.test_method)
worker.start()
#staticmethod
def test_method():
res = [i*i for i in range(100500)]
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
What's more weird is that it doesn't crash when you debug the application for some reason.
What am I missing here?
Edit
I could get much from the crashdump, as I don't have symbols for QT. But it looks like the crash happens inside QtCore.dll
ExceptionAddress: 00000000632d4669 (Qt5Core!QThread::start+0x0000000000000229)
The problem is that you didn't save a reference to the thread so it was deleted as soon as you exited handleButton. If you save a reference, that begs the question of how to handle its lifespan.
QThread is more than just a wrapper to a system thread - it implements other services that let you wire a thread into your GUI. You can use its finished handler to signal the widget when it terminates to do any cleanup.
In this example, I save the worker as self.worker2 and block starting the worker a second time until the first one completes.
import PyQt5
import PyQt5.QtCore as QtCore
from PyQt5.QtWidgets import *
import time
class Worker(QtCore.QThread):
def __init__(self, method_to_run):
super(Worker, self).__init__()
self.method = method_to_run
def run(self):
self.method()
class Window(QWidget):
def __init__(self):
super().__init__()
self.button = QPushButton('Test', self)
self.label = QLabel(self)
self.button.clicked.connect(self.handleButton)
layout = QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.button)
self.worker = Worker(self.test_method)
self.worker.start()
self.worker2 = None
def handleButton(self):
self.label.setText('Button Clicked!')
# likely better to disable the button instead... but
# this shows events in action.
if self.worker2:
self.label.setText('Worker already running')
else:
self.worker2 = Worker(self.test_method)
self.worker2.finished.connect(self.handle_worker2_done)
self.worker2.start()
def handle_worker2_done(self):
self.worker2 = None
self.label.setText('Worker done')
#staticmethod
def test_method():
#res = [i*i for i in range(100500)]
time.sleep(3)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Your click triggered thread isn't actually trying to do work in a thread. You called the method, rather than passing the method as an argument, so you're trying to use the return value of test_method (None) as the method to run.
Change:
worker = Worker(self.test_method()) # Calls test_method and tries to run None
to:
worker = Worker(self.test_method) # Don't call test_method
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_()