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.
Related
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()
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_()
I'm trying to learn how to use QThreads in a PyQt Gui application. I have stuff that runs for a while, with (usually) points where I could update a Gui, but I would like to split the main work out to its own thread (sometimes stuff gets stuck, and it would be nice to eventually have a cancel/try again button, which obviously doesn't work if the Gui is frozen because the Main Loop is blocked).
I've read https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/. That page says that re-implementing the run method is not the way to do it. The problem I am having is finding a PyQt example that has a main thread doing the Gui and a worker thread that does not do it that way. The blog post is for C++, so while it's examples do help, I'm still a little lost. Can someone please point me to an example of the right way to do it in Python?
Here is a working example of a separate worker thread which can send and receive signals to allow it to communicate with a GUI.
I made two simple buttons, one which starts a long calculation in a separate thread, and one which immediately terminates the calculation and resets the worker thread.
Forcibly terminating a thread as is done here is not generally the best way to do things, but there are situations in which always gracefully exiting is not an option.
from PyQt4 import QtGui, QtCore
import sys
import random
class Example(QtCore.QObject):
signalStatus = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(self.__class__, self).__init__(parent)
# Create a gui object.
self.gui = Window()
# Create a new worker thread.
self.createWorkerThread()
# Make any cross object connections.
self._connectSignals()
self.gui.show()
def _connectSignals(self):
self.gui.button_cancel.clicked.connect(self.forceWorkerReset)
self.signalStatus.connect(self.gui.updateStatus)
self.parent().aboutToQuit.connect(self.forceWorkerQuit)
def createWorkerThread(self):
# Setup the worker object and the worker_thread.
self.worker = WorkerObject()
self.worker_thread = QtCore.QThread()
self.worker.moveToThread(self.worker_thread)
self.worker_thread.start()
# Connect any worker signals
self.worker.signalStatus.connect(self.gui.updateStatus)
self.gui.button_start.clicked.connect(self.worker.startWork)
def forceWorkerReset(self):
if self.worker_thread.isRunning():
print('Terminating thread.')
self.worker_thread.terminate()
print('Waiting for thread termination.')
self.worker_thread.wait()
self.signalStatus.emit('Idle.')
print('building new working object.')
self.createWorkerThread()
def forceWorkerQuit(self):
if self.worker_thread.isRunning():
self.worker_thread.terminate()
self.worker_thread.wait()
class WorkerObject(QtCore.QObject):
signalStatus = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(self.__class__, self).__init__(parent)
#QtCore.pyqtSlot()
def startWork(self):
for ii in range(7):
number = random.randint(0,5000**ii)
self.signalStatus.emit('Iteration: {}, Factoring: {}'.format(ii, number))
factors = self.primeFactors(number)
print('Number: ', number, 'Factors: ', factors)
self.signalStatus.emit('Idle.')
def primeFactors(self, n):
i = 2
factors = []
while i * i <= n:
if n % i:
i += 1
else:
n //= i
factors.append(i)
if n > 1:
factors.append(n)
return factors
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.button_start = QtGui.QPushButton('Start', self)
self.button_cancel = QtGui.QPushButton('Cancel', self)
self.label_status = QtGui.QLabel('', self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button_start)
layout.addWidget(self.button_cancel)
layout.addWidget(self.label_status)
self.setFixedSize(400, 200)
#QtCore.pyqtSlot(str)
def updateStatus(self, status):
self.label_status.setText(status)
if __name__=='__main__':
app = QtGui.QApplication(sys.argv)
example = Example(app)
sys.exit(app.exec_())
You are right that it is a good thing to have a worker thread doing the processing while main thread is doing the GUI. Also, PyQt is providing thread instrumentation with a signal/slot mechanism that is thread safe.
This may sound of interest. In their example, they build a GUI
import sys, time
from PyQt4 import QtCore, QtGui
class MyApp(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(300, 300, 280, 600)
self.setWindowTitle('threads')
self.layout = QtGui.QVBoxLayout(self)
self.testButton = QtGui.QPushButton("test")
self.connect(self.testButton, QtCore.SIGNAL("released()"), self.test)
self.listwidget = QtGui.QListWidget(self)
self.layout.addWidget(self.testButton)
self.layout.addWidget(self.listwidget)
def add(self, text):
""" Add item to list widget """
print "Add: " + text
self.listwidget.addItem(text)
self.listwidget.sortItems()
def addBatch(self,text="test",iters=6,delay=0.3):
""" Add several items to list widget """
for i in range(iters):
time.sleep(delay) # artificial time delay
self.add(text+" "+str(i))
def test(self):
self.listwidget.clear()
# adding entries just from main application: locks ui
self.addBatch("_non_thread",iters=6,delay=0.3)
(simple ui containing a list widget which we will add some items to by clicking a button)
You may then create our own thread class, one example is
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
for i in range(6):
time.sleep(0.3) # artificial time delay
self.emit( QtCore.SIGNAL('update(QString)'), "from work thread " + str(i) )
self.terminate()
You do redefine the run() method. You may find an alternative to terminate(), see the tutorial.
In my opinion, by far the best explanation, with example code which is initially unresponsive, and is then improved, is to be found here.
Note that this does indeed use the desired (non-subclassed) QThread and moveToThread approach, which the article claims to be the preferred approach.
The above linked page also provides the PyQt5 equivalent to the C Qt page giving the definitive explanation by Maya Posch from 2011. I think she was probably using Qt4 at the time, but that page is still applicable in Qt5 (hence PyQt5) and well worth studying in depth, including many of the comments (and her replies).
Just in case the first link above one day goes 404 (which would be terrible!), this is the essential Python code which is equivalent to Maya's C code:
self.thread = QtCore.QThread()
# Step 3: Create a worker object
self.worker = Worker()
# Step 4: Move worker to the thread
self.worker.moveToThread(self.thread)
# Step 5: Connect signals and slots
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.worker.progress.connect(self.reportProgress)
# Step 6: Start the thread
self.thread.start()
# Final resets
self.longRunningBtn.setEnabled(False)
self.thread.finished.connect(
lambda: self.longRunningBtn.setEnabled(True)
)
self.thread.finished.connect(
lambda: self.stepLabel.setText("Long-Running Step: 0")
)
NB self in the example on that page is the QMainWindow object. I think you may have to be quite careful about what you attach QThread instances to as properties: instances which are destroyed when they go out of scope, but which have a QThread property, or indeed a local QThread instance which goes out of scope, seem to be capable of causing some inexplicable Python crashes, which aren't picked up by sys.excepthook (or the sys.unraisablehook). Caution advised.
... where Worker looks something like this:
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
progress = QtCore.pyqtSignal(int)
def run(self):
"""Long-running task."""
for i in range(5):
sleep(1)
self.progress.emit(i + 1)
self.finished.emit()