I made a small test application to practice threading - on the first launch it works fine, with the loop printing to the console while the gui is still running. However, the kernel crashes and restarts if I close the app and then open it again afterwards.
I've had this issue before with pyqt5 applications, and it's usually solved when I add the QCoreApplication.instance() section at the end of my script. I was wondering whether this time there was an error with the way I implemented my threading.
import sys
import time
from PyQt5 import QtWidgets
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtCore import QCoreApplication
class Worker(QObject):
finished = pyqtSignal()
def __init__(self):
super(Worker, self).__init__()
self.working = True
def work(self):
while self.working:
# do stuff here
print("I'm running")
time.sleep(1)
self.finished.emit() # alert our gui that the loop stopped
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(50, 50, 200, 150)
self.setWindowTitle("Program")
self.startbtn = QtWidgets.QPushButton("Start", self)
self.startbtn.resize(self.startbtn.minimumSizeHint())
self.startbtn.move(50, 50)
self.stopbtn = QtWidgets.QPushButton("Stop", self)
self.stopbtn.move(50, 100)
self.stopbtn.resize(self.stopbtn.minimumSizeHint())
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.startbtn.clicked.connect(self.thread.start) # start the thread when we click the start button
self.thread.started.connect(self.worker.work) # begin our worker object's loop
self.stopbtn.clicked.connect(self.stop_loop) # stop the loop on the stop button click
self.worker.finished.connect(self.loop_finished) # do something in the gui when the worker loop ends
def stop_loop(self):
self.worker.working = False
def loop_finished(self):
print('Looped Finished')
if __name__ == "__main__":
app = QCoreApplication.instance() # Fixes error with kernel crashing on second run of application
if app is None: # PyQt doesn't like multiple QApplications in the same process, therefore
app = QtWidgets.QApplication(sys.argv) # apart from the initial run, the first QApplication instance will run
window = Window()
window.show()
app.exec_()
Related
I have got this problem. I´m trying to set text on a lineEdit object on pyqt4, then wait for a few seconds and changing the text of the same lineEdit. For this I´m using the time.sleep() function given on the python Time module. But my problem is that instead of setting the text, then waiting and finally rewrite the text on the lineEdit, it just waits the time it´s supposed to sleep and only shows the final text. My code is as follows:
from PyQt4 import QtGui
from gui import *
class Ventana(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setupUi(self)
self.button.clicked.connect(self.testSleep)
def testSleep(self):
import time
self.lineEdit.setText('Start')
time.sleep(2)
self.lineEdit.setText('Stop')
def mainLoop(self, app ):
sys.exit( app.exec_())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Ventana()
window.show()
sys.exit(app.exec_())
You can't use time.sleep here because that freezes the GUI thread, so the GUI will be completely frozen during this time.
You should probably use a QTimer and use it's timeout signal to schedule a signal for deferred delivery, or it's singleShot method.
For example (adapted your code to make it run without dependencies):
from PyQt4 import QtGui, QtCore
class Ventana(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setLayout(QtGui.QVBoxLayout())
self.lineEdit = QtGui.QLineEdit(self)
self.button = QtGui.QPushButton('clickme', self)
self.layout().addWidget(self.lineEdit)
self.layout().addWidget(self.button)
self.button.clicked.connect(self.testSleep)
def testSleep(self):
self.lineEdit.setText('Start')
QtCore.QTimer.singleShot(2000, lambda: self.lineEdit.setText('End'))
def mainLoop(self, app ):
sys.exit( app.exec_())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Ventana()
window.show()
sys.exit(app.exec_())
Also, take a look at the QThread sleep() function, it puts the current thread to sleep and allows other threads to run. https://doc.qt.io/qt-5/qthread.html#sleep
You can't use time.sleep here because that freezes the GUI thread, so the GUI will be completely frozen during this time.You can use QtTest module rather than time.sleep().
from PyQt4 import QtTest
QtTest.QTest.qWait(msecs)
So your code should look like:
from PyQt4 import QtGui,QtTest
from gui import *
class Ventana(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setupUi(self)
self.button.clicked.connect(self.testSleep)
def testSleep(self):
import time
self.lineEdit.setText('Start')
QtTest.QTest.qWait(2000)
self.lineEdit.setText('Stop')
def mainLoop(self, app ):
sys.exit( app.exec_())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Ventana()
window.show()
sys.exit(app.exec_())
When calling a thread class in PyQt5, the application crashes with error: QThread: Destroyed while thread is still running if I don't add
def __del__(self):
self.wait()
After adding the above statement to the thread class, the application runs but stalls until the thread has finished executing (basically making the thread useless). I'm running macOS Catalina 10.15.6, Intel i9, Python 3.7.4. Any way to resolve this? Here is the code (the ui window only has one button in it):
from PyQt5.QtWidgets import QMainWindow
from PyQt5 import QtWidgets, uic
from PyQt5.QtCore import QThread, pyqtSignal
import sys, time
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
uic.loadUi('main.ui', self)
self.btn1.clicked.connect(self.run_worker)
def run_worker(self):
worker = Worker_thread()
worker.start()
class Worker_thread(QThread):
def __init__(self):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
time.sleep(10)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
win = Main()
win.show()
sys.exit(app.exec_())
The problem is simple: The "worker" variable is a local variable that when its scope ends it is eliminated, in this case the scope is the "run_worker" function. When the "run" method is eliminated, it is not executed in the secondary thread that manages the QThread but in the main thread blocking the GUI.
The solution is to extend its scope, for example by making class attribute:
def run_worker(self):
self.worker = Worker_thread()
self.worker.start()
Note: The problem is not OS dependent.
I'm trying to make the background worker start with the start button and stop with the stop button. When I push start the program starts but I can't stop it and it also exits the gui when completed. Sorry, I am a novice just trying to learn. I found this example here and I am just trying to expand on it.
Background thread with QThread in PyQt
# main.py
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout, QPushButton
import sys
import worker
class Form(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("0") # start out at 0.
##### I added the stuff below 6/8/2020 ####################################
k = 2
self.button1 = QPushButton("Push to start") # create start button
self.button2 = QPushButton("Push to Stop") # create a stop button.
self.button1.clicked.connect(self.start) # connect button to function
self.button2.clicked.connect(self.stop) # connect button to function
########################################################################
# 1 - create Worker and Thread inside the Form
self.obj = worker.Worker() # no parent! #### Calls to worker.py
self.thread = QThread() # no parent! #### Creates an empty thread
# 2 - Connect Worker`s Signals to Form method slots to post data.
self.obj.intReady.connect(self.onIntReady) # connects the definition below
# 3 - Move the Worker object to the Thread object
self.obj.moveToThread(self.thread)
# 4 - Connect Worker Signals to the Thread slots
self.obj.finished.connect(self.thread.quit)
# 5 - Connect Thread started signal to Worker operational slot method
#self.thread.started.connect(self.obj.procCounter(k)) #### starts the counter in worker.py
# 6 - Start the thread
self.thread.start()
# 7 - Start the form
self.initUI()
def start(self):
k=2
self.obj.procCounter(k)
def stop(self):
self.thread.quit()
def initUI(self):
grid = QGridLayout()
self.setLayout(grid)
grid.addWidget(self.label,0,1)
grid.addWidget(self.button1, 0, 0)
grid.addWidget(self.button2, 0, 2)
self.move(300, 150)
self.setWindowTitle('thread test')
self.show()
def onIntReady(self, i):
self.label.setText("{}".format(i))
print(i)
app = QApplication(sys.argv)
form = Form()
sys.exit(app.exec_())
# worker.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time
class Worker(QObject): # this is called by Worker.worker() in main.py
finished = pyqtSignal() # sets up to emit a signal back to main.py
intReady = pyqtSignal(int) # emits an integer to main.py def onIntReady
#pyqtSlot()
def procCounter(self,k): # A slot takes no params
for i in range(1, 10):
time.sleep(1)
self.intReady.emit(i*k) # emits the signal to onIntReady
#def procCounter(self): # A slot takes no params
# for i in range(1, 100):
# time.sleep(1)
# self.intReady.emit(i)
self.finished.emit()
I have an app that starts the GUI and performs "the heavy part" of the code in another thread using QThread. In this thread I emit a SIGNAL that is connected to the GUI Class and that performs addItem on QListWidget.
There are a massive "signaling" from this thread to the GUI and it "freeze".
Is there a way to avoid this? Have I to use another mini GUI in different thread only for QListWidget?
Thanks
EDIT:
This is the thread that execute the heavy logic
class YourThreadName(QThread):
def __init__(self, some variables):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
# Here there is a for cycle that emits a SIGNAL
for ... :
...
self.emit(SIGNAL("needed_variable"), needed_variable)
...
In the GUI Class there are some methods, particularly:
class GUI(QtGui.QMainWindow, GUI.Ui_MainWindow):
def __init__(self, parent=None):
super(GUI, self).__init__(parent)
self.setupUi(self)
def ... (self):
...
def start_main_code(self):
self.new_thread = YourThreadName(some variables)
self.connect(self.new_thread, SIGNAL("finished()"), self.done)
self.connect(self.new_thread, SIGNAL("needed_variable"), self.show_variable)
self.new_thread.start()
def show_variable(self, data):
self.QListWidget_object.addItem(data)
def ... (self):
...
The script below is a Minimal, Complete, and Verifiable Example based on the information currently given in your question and comments. It emits data from a worker thread every 10ms and updates a list-widget in the GUI. On my Linux system (using Python-3.6.3, Qt-4.8.7 and PyQt-4.12.1) it does not block or freeze the GUI. There is obviously some flickering whilst the list-widget is being updated, but I am able to select items, scroll up and down, click the button, etc. And if I increase the sleep to 25ms, I don't even get any flickering.
UPDATE:
The performance can be improved by using setUniformItemSizes and sending the messages in batches. On my system, after a slight initial delay, the list populates with fifty thousand items almost instantly.
import sys
from PyQt4 import QtCore, QtGui
class Worker(QtCore.QThread):
message = QtCore.pyqtSignal(object)
def run(self):
batch = []
for index in range(50000):
if len(batch) < 200:
batch.append(index)
continue
self.message.emit(batch)
batch = []
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.listWidget = QtGui.QListWidget()
self.listWidget.setUniformItemSizes(True)
self.button = QtGui.QPushButton('Start')
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.listWidget)
layout.addWidget(self.button)
self.worker = Worker()
self.worker.message.connect(self.handleMessages)
def handleMessages(self, batch):
for message in batch:
self.listWidget.addItem('Item (%s)' % message)
def handleButton(self):
if not self.worker.isRunning():
self.worker.start()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 50, 200, 400)
window.show()
sys.exit(app.exec_())
I have a pyqt GUI and a method[BigramClassification()]which causes the GUI to hang for several seconds. Therefore, i figured out threading need to be used. So after reading several tutorials i came up with the following code.
import sys,os
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import QThread
import time
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.lblHistory.setPixmap(QtGui.QPixmap(os.getcwd() + "/historygraph.png"))
self.workerThread=WorkingThread()
self.ui.pushButton.clicked.connect(self.generateDetails)
self.ui.btnsubmitsettings.clicked.connect(self.addDetails)
def generateDetails(self):
self.workerThread.start()
self.ui.lblHistory.setPixmap(QtGui.QPixmap(os.getcwd() + "/historygraph.png"))
self.addPiechart()
self.addWordCloud()
self.summaryText()
def addPiechart(self):
print ("Added")
def addWordCloud(self):
print ("Added")
def addDetails(self):
def summaryText(self):
print("Added")
class WorkingThread(QThread):
def __init__(self, parent=None):
super(self.__class__, self).__init__(parent)
def run(self):
BigramsClassifier()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MyForm()
myapp.show()
sys.exit(app.exec_())
The problem i have is when i run this and click on pushButton the thread starts but also executes the methods after the start() as seen in def generateDetails(self): I need to prepare this code so that the methods in def generateDetails(self): executes after the thread is done executing with the heavy method BigramClassification()execution.
Summary How can i stop the auto execution of the methods in def generateDetails(self): but only after the method BigramClassification() is completed.
EDIT This error is thrown up when i try to close the GUI.
Connect a slot to the thread's finished signal which can performs other actions once the long-running task is completed:
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
...
self.workerThread = WorkingThread()
self.workerThread.finished.connect(self.doOtherStuff)
...
def generateDetails(self):
if not self.workerThread.isRunning():
self.workerThread.start()
def doOtherStuff(self):
self.ui.lblHistory.setPixmap(QtGui.QPixmap(os.getcwd() + "/historygraph.png"))
self.addPiechart()
self.addWordCloud()
self.summaryText()