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()
Related
i'm stucked on a Qtimer that don't start, so the number that i need to update on the GUI is never showed.
i don't want to use a while loop inside the code because i need to change some values in real time without any problem.
i really don't understand why it don't start... or better, it start, but don't run the function update_label.
any suggestion?
Thanks a lot for your time!
here is my code:
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setObjectName("MainWindow")
self.resize(1300, 768)
self.setMinimumSize(1300,768)
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("Bot", "Bot"))
self.number = QtWidgets.QLabel(self)
self.number .setGeometry(QtCore.QRect(1100, 10, 300, 20))
self.number .setObjectName("number")
self.show()
self.threadpool = QThreadPool()
self.updater = Updater()
self.threadpool.start(self.updater)
self.updater.update_progress.latest_number.connect(self.update_number)
#pyqtSlot(str)
def update_number(self, val):
x = str(val)
self.number.setText("Current block: " + x)
class Updater(QRunnable):
def __init__(self):
super(Updater, self).__init__()
self.update_progress = WorkerSignal()
print("started")
def run(self):
self.timer = QTimer()
self.timer.setInterval(2000)
self.timer.timeout.connect(self.update_label)
self.timer.start()
def update_label(self):
provider = configfile.provider
number = str(nmbr.latest_numer(provider))
self.update_progress.latest_number.emit(number)
print("update label started")
After tyring to understand your script, I reached the conclusion that QRunnable is automatically released (or stopped) once Updater.run(self) reaches its end.
But this is supposed to happen, because that's why you should use a QThreadPool in the first place. It is supposed to recycle expired threads in the background of the application.
So, as you're running a QTimer in the thread, QThreadPool thinks that Updater thread is dead, while in reality, the timer is still running on the background of a background thread. So it does its job and releases Updater from the Thread pool.
What you must do in order to keep both the Timer and Updater alive is to manager the Updater thread's lifecycle yourself.
So instead of using QThreadPool and QRunnable, you must go one level lower in terms of abstraction, and use the QThread and QObject themselves in order to control when the thread is going to be stopped.
from PySide2 import QtWidgets, QtCore
from PySide2.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout
from PySide2.QtCore import Qt, QThreadPool, QRunnable, Slot, Signal, QObject, QTimer, QThread
class WorkerSignal(QObject):
# Changed pyqtSignal to Signal
latest_number = Signal(str)
class Worker(QObject):
def __init__(self):
QObject.__init__(self)
self.update_progress = WorkerSignal()
def update_label(self):
print("update label started")
try:
provider = configfile.provider
number = str(nmbr.latest_numer(provider))
self.update_progress.latest_number.emit(number)
except:
print("Error, but just for testing...")
self.update_progress.latest_number.emit("0")
def main(self):
print('Init worker')
self.timer = QTimer()
self.timer.setInterval(2000)
self.timer.timeout.connect(self.update_label)
self.thread().finished.connect(self.timer.stop)
self.timer.start()
class Updater(QThread):
def __init__(self):
QThread.__init__(self)
self.worker = Worker()
# Move worker to another thread, to execute in parallel
self.worker.moveToThread(self)
# Set which method from Worker that should excute on the other thread.
# In this case: Worker.main
self.started.connect(self.worker.main)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
QMainWindow.__init__(self, *args, **kwargs)
self.setObjectName("MainWindow")
self.resize(1300, 768)
self.setMinimumSize(1300,768)
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("SniperBot", "SniperBot"))
self.number = QtWidgets.QLabel(self)
self.number.setGeometry(QtCore.QRect(1100, 10, 300, 20))
self.number.setObjectName("number")
# I did this to see the label, as it was not attached to any
# widget's layout on your script.
widget = QWidget()
layout = QVBoxLayout()
widget.setLayout(layout)
layout.addWidget(self.number)
self.setCentralWidget(widget)
self.scene = widget
self.show()
# Create the Updater thread.
self.updater = Updater()
# Connect the Worker's signal to self.update_number
self.updater.worker.update_progress.latest_number.connect(self.update_number)
# Start the thread
self.updater.start()
def closeEvent(self, evt):
# Before exiting from the application, remember, we control now
# the Updater Thread's lifecycle. So it's our job to stop the thread
# and wait for it to finish properly.
self.updater.quit()
self.updater.wait()
# Accept the event. Exit the application.
evt.accept()
# Changed pyqtSlot to Slot
#Slot(str)
def update_number(self, val):
x = str(val)
print("UPDATE NUMBER: ", x)
self.number.setText("Current block: " + x)
if __name__ == '__main__':
app = QApplication()
win = MainWindow()
win.show()
app.exec_()
What you can do now is follow this pattern and start implementing from here. There are many tutorials that use the moveToThread method. Just be careful to not let the thread or any object be recycled by the python's garbage collector, in order to avoid many other problems.
The script is working, however I use PySide2 instead of PyQt5. So some variable and modules names may change, so just rename them when you try to run the script on your end.
There were a few errors on Worker.update_label(self), which is just a copied script from the old Updater.update_label(self). I don't know what are the variables: configfile or nmbr as they are not initialized on your post. So I made a try-except block to handle the error and make the script work just for testing.
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()
I am making window application in PyQt5 and I want to parse data from XML file in the backgrond and send it to second class. I want to use threads with queue to handle between them. When I want to display window app i see black window. It would be nice to use python threads but i tried to do it on QThread and it is not working too idk why...
This is code example
import queue
import sys
import threading
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import *
class Test_generator:
def __init__(self, queue_obj):
super().__init__()
self.queue_obj = queue_obj
#self.parser_thread = threading.Thread(target=self.parser())
def sender(self):
for _ in range(100):
string ="test"
self.queue_obj.put(string)# send to queue
time.sleep(1)
#print(string)
class Test_collectioner:
def __init__(self,queue_obj):
self.queue_obj = queue_obj
def get_data(self):
collection = []
while self.queue_obj.empty() is False:
print("xd")
print(self.queue_obj.get(), "xd")
#I found this example in the internet(Not working)
class Threaded(QThread):
result = pyqtSignal(int)
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
#pyqtSlot(int)
def run(self):
while True:
print("test")
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.GUI()
self.setWindowTitle("PyQt5 app")
global q
q = queue.Queue()
# BLACKSCREEN (for Test_generator)
test_generator_obj = Test_generator(q)
test_collectioner_obj = Test_collectioner(q)
t1 = threading.Thread(target=test_generator_obj.sender())
t2 = threading.Thread(target=test_collectioner_obj.get_data())
t1.start()
t2.start()
# BLACKSCREEN TOO
"""self.thread = QThread()
self.threaded = Threaded()
self.thread.started.connect(self.threaded.run())
self.threaded.moveToThread(self.thread)
qApp.aboutToQuit.connect(self.thread.quit)
self.thread.start()"""
def GUI(self):
self.showMaximized()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
There are two main problems in your code:
you're executing the methods used for the thread, but you should use the callable object instead; you did the same mistake for the QThread, since connections to signals also expects a callable, but you're actually executing the run function from the main thread (completely blocking everything), since you're using the parentheses; those lines should be like this:
t1 = threading.Thread(target=test_generator_obj.sender)
t2 = threading.Thread(target=test_collectioner_obj.get_data)
or, for the QThread:
self.thread.started.connect(self.threaded.run)
due to the python GIL, multithreading only releases control to the other threads whenever it allows to, but the while cycle you're using prevents that; adding a small sleep function ensures that control is periodically returned to the main thread;
Other problems also exist:
you're already using a subclass of QThread, so there's no use in using another QThread and move your subclass to it;
even assuming that an object is moved to another thread, the slot should not be decorated with arguments, since the started signal of a QThread doesn't have any; also consider that slot decorators are rarely required;
the thread quit() only stops the event loop of the thread, but if run is overridden no event loop is actually started; if you want to stop a thread with a run implementation, a running flag should be used instead; in any other situation, quitting the application is normally enough to stop everything;
Note that if you want to interact with the UI, you can only use QThread and custom signals, and basic python threading doesn't provide a suitable mechanism.
class Threaded(QThread):
result = pyqtSignal(int)
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
def run(self):
self.keepGoing = True
while self.keepGoing:
print("test")
self.msleep(1)
def stop(self):
self.keepGoing = False
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.threaded = Threaded()
self.threaded.start()
qApp.aboutToQuit.connect(self.threaded.stop)
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 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_()