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)
Related
Question
I find a issue in QThread, if you use super().run() in your QThread obj, it will not emit the finished signal. And, I find the definition of QThread.run(), it is no different. So I want to ask why it happened.(I create a test for threading.Thread, it is normal).
Screenshot
The definition of QThread.run()
Code
threading.Thread Code
#!/usr/bin/env python3
import threading
class InThreading(threading.Thread):
def __init__(self):
super(InThreading, self).__init__()
def run(self) -> None:
print('threading thread')
print(threading.enumerate())
return super().run()
if __name__ == '__main__':
inthreadng = InThreading()
print(threading.enumerate())
inthreadng.start()
inthreadng.join()
print(threading.enumerate())
QThread Code
#!/usr/bin/env python3
import sys
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QMainWindow
class User_Interface(QMainWindow):
def __init__(self):
super(User_Interface, self).__init__()
self.usage()
def usage(self):
qtthread = QtThread(self)
qtthread.start()
qtthread.finished.connect(lambda: print('qtthrediing finished!'))
class QtThread(QThread):
def __init__(self, parent):
super(QtThread, self).__init__(parent=parent)
def run(self) -> None:
print('created QThread Example')
return super().run()
if __name__ == '__main__':
app = QApplication(sys.argv)
ui = User_Interface()
ui.show()
sys.exit(app.exec_())
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_())
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 call a function after the GUI displays. If I run function in init it prevents gui from displaying until after it is completed.
class MyApp(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.function() #waits for this to finish until gui displayed
def function(self):
self.guiBox.setValue(initData)
#inits stuff, takes 5 seconds
The function initializes a piece of equipment via serial port... It takes s few seconds, and it takes gui attributes and updates gui display boxes.
Add single shot timer 1 ms and after call function
class MyApp(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
QTimer.singleShot(1,self.function) #waits for this to finish until gui displayed
def function(self):
self.guiBox.setValue(initData)
#inits stuff, takes 5 seconds
Time-consuming tasks are blocking, and this goes against the natural way of working on the GUI, an option is to use qApp.processEvents(), for example:
def function(self):
self.guiBox.setValue(initData)
code1
QtWidgets.qApp.processEvents()
code2
QtWidgets.qApp.processEvents()
...
I would recommend QThreads, especially if you are performing a ton of other actions in your "function." This example isn't the only way to thread in PyQt, but thought an example where you are able to pass data back and forth between the thread and the main gui would be best.
import os
import sys
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget
from PyQt5.QtCore import QUrl, QEventLoop, QThread, QObject, pyqtSlot, pyqtSignal
from PyQt5.QtWebEngineWidgets import QWebEngineView
class time_consuming_function(QObject):
def __init__(self, widget):
super(time_consuming_function, self).__init__()
self.widget = widget
self.run_trigger.connect(self.run)
run_trigger = pyqtSignal(int, int)
#pyqtSlot(int, int)
def run(self, int1, int2):
print("In Time Consuming Function")
for i in range(100000000):
i*i
print("Finished with Time Consuming Function")
self.widget.someTrigger.emit([1, 2, 3])
class WebPage(QWebEngineView):
def __init__(self):
QWebEngineView.__init__(self)
self.load(QUrl("https://www.google.com"))
self.loadFinished.connect(self._on_load_finished)
self.someTrigger.connect(self.gui_response)
self.thread = QThread()
self.thread.start()
self.consume = time_consuming_function(self)
self.consume.moveToThread(self.thread)
self.consume.run_trigger.emit(1,1)
someTrigger = pyqtSignal(list)
def _on_load_finished(self):
print("Finished Loading")
def gui_response(self, data):
print("Responding to GUI: ", str(data))
if __name__ == "__main__":
app = QApplication(sys.argv)
web = WebPage()
web.show()
sys.exit(app.exec_())
I'd say the easiest way without using thread and related would be to verify if the last event of showing up the window was executed and then call your method.
You'd have something like that:
import sys
from PyQt5.QtCore import QEvent
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setFixedSize(500, 500)
self.move(300, 50)
def print_1_bi(self):
for i in range(10**9): print(i)
def event(self, event):
if event.type() == QEvent.InputMethodQuery:
self.print_1_bi()
return super(MainWindow, self).event(event)
if __name__ == "__main__":
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
Note: Remember that even though the UI already showed up your application will be waiting your method to finish, if it's a problem you'll have to use process like the guys told you to in the other answers.
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