I want to use PySide2 Qtcore.Qthread because of Qtcore.Signal, but I end up with this error:
Process finished with exit code -1073740791
from PySide2.QtCore import QThread
class Thread(QThread):
def run(self):
print('task started')
k = 0
for i in range(10000):
for j in range(5000):
k += 1
print('task finished')
Thread().start()
expect to have those prints but I have this error:
Process finished with exit code -1073740791
Update:
so, why this code also throw the same error?
class Thread(QThread):
done = Signal()
def __init__(self):
super(Thread, self).__init__()
def run(self):
print('task started')
k = 0
for i in range(10000):
for j in range(5000):
k += 1
print('task finished')
self.done.emit()
class Widget(QtWidgets.QWidget):
def __init__(self):
super(Widget, self).__init__()
btn = QtWidgets.QPushButton('test', parent=self)
btn.clicked.connect(self.clicked)
btn.show()
def clicked(self):
t = Thread()
t.done.connect(self.done)
t.start()
def done(self):
print('done')
app = QtWidgets.QApplication()
window = Widget()
window.show()
sys.exit(app.exec_())
Explanation
If you run your code in a CMD/Terminal you will get the following error:
QThread: Destroyed while thread is still running
Aborted (core dumped)
And the error is caused because the thread is destroyed while it is still running since it is a local variable, on the other hand QThread needs an event loop to run
Solution
import sys
from PySide2.QtCore import QCoreApplication, QThread
class Thread(QThread):
def run(self):
print("task started")
k = 0
for i in range(10000):
for j in range(5000):
k += 1
print("task finished")
if __name__ == "__main__":
# create event loop
app = QCoreApplication(sys.argv)
th = Thread()
th.start()
th.finished.connect(QCoreApplication.quit)
sys.exit(app.exec_())
Update:
"t" is a local variable that will be eliminated after executing clicked causing the same problem as your initial code, the solution is to prevent it from being destroyed instantly and for this there are 2 options:
Make a "t" class attribute
def clicked(self):
self.t = Thread()
self.t.done.connect(self.done)
self.t.start()
Store the QThread in a container that has a longer life cycle:
class Widget(QtWidgets.QWidget):
def __init__(self):
super(Widget, self).__init__()
btn = QtWidgets.QPushButton('test', parent=self)
btn.clicked.connect(self.clicked)
self.container = []
def clicked(self):
t = Thread()
t.done.connect(self.done)
t.start()
self.container.append(t)
# ...
Pass it as a parent to "self" but for this it is necessary that Thread allow to receive so you must implement that in the constructor:
class Thread(QThread):
done = Signal()
def __init__(self, parent=None):
super(Thread, self).__init__(parent)
# ...
def clicked(self):
t = Thread(self)
t.done.connect(self.done)
t.start()
I found the solution but I don't know why I should do this. the thread should a part of the class.
self.t = Thread()
self.t.done.connect(self.done)
self.t.start()
Related
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
loadUi('wind.ui', self)
self.startButton.clicked.connect(self.start_screen)
self.stopButton.clicked.connect(self.stop_screen)
self.screen_thread = Screenshot()
def start_screen(self):
self.screen_thread.start()
self.screen_thread.any_signal.connect(self.myfunction)
def stop_screen(self):
self.screen_thread.stop()
def myfunction(self,value):
vl = value
print(vl, threading.active_count())
class Screenshot(QThread):
any_signal = pyqtSignal(int)
def __init__(self, parent = None):
super(Screenshot, self).__init__(parent)
def run(self):
value = 1
print('process start')
print(threading.active_count())
print(threading.enumerate())
while(True):
value += 1
time.sleep(1)
self.any_signal.emit(value)
def stop(self):
print('process stop')
self.terminate()
print(self.isRunning())
def application():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
application()
There are two start and stop buttons. when I run it for the first time, everything is ok, the value is printed once and incremented. then I press stop, everything stops. when I press start a second time, the result is printed twice. the third time three times, and so on. I thought that new threads were being added and entered a command to monitor threads. but only one thread is shown each time. where is the mistake?
I thought that new threads were being added and entered a command to monitor threads. but only one thread is shown each time.
Only one thread is shown each time because you only create one thread: self.screen_thread = Screenshot()
Each time you click the start button it starts the thread (calling .start() once a thread has started does nothing), and it connects the self.myfunction slot to the any_signal. Because you can connect the same slot multiple times to the same signal, it just gets called multiple times.
If you want multiple threads, then you will need to create them, and keep track of them. Something like this should do:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
loadUi('wind.ui', self)
self.startButton.clicked.connect(self.start_screen)
self.stopButton.clicked.connect(self.stop_screen)
self.threads = []
def start_screen(self):
thread = Screenshot()
thread.any_signal.connect(self.myfunction)
thread.start()
self.threads.append(thread)
def stop_screen(self):
for thread in self.threads:
thread.stop()
self.threads = []
def myfunction(self,value):
vl = value
print(vl, threading.active_count())
class Screenshot(QThread):
any_signal = pyqtSignal(int)
def __init__(self, parent = None):
super(Screenshot, self).__init__(parent)
def run(self):
value = 1
print('process start')
print(threading.active_count())
print(threading.enumerate())
while(True):
value += 1
time.sleep(1)
self.any_signal.emit(value)
def stop(self):
print('process stop')
self.terminate()
print(self.isRunning())
def application():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
application()
Note that the use of QThread.terminate is discouraged:
Warning: This function is dangerous and its use is discouraged. The thread can be terminated at any point in its code path. Threads can be terminated while modifying data. There is no chance for the thread to clean up after itself, unlock any held mutexes, etc. In short, use this function only if absolutely necessary.
Instead, call QThread.requestInterruption and regularly check for an interruption request within the thread with QThread.isInterruptionRequested.
I've inhereted a GUI code which is structured something like this:
any button signal triggers a slot, those slots then call an external process to receive information and wait until that process finishes, then the slot proceeds. the issue is, this external process takes between 0.5 to 60 seconds, and in that time the GUI freezes. i'm struggling to find a good way to seperate this process call to a different thread or QProcess (that way i will not block the main event loop) and then return and continue the relevent slot (or function) from that same point with the information received from that external slow process. generators seem like something that should go here, but im struggling to figure how to restructure the code so this will work.
any suggestions or ideas? is there a Qt way to "yield" a function until that process completes and then continue that function?
Psudo code of the current structure:
button1.clicked.connect(slot1)
button2.clicked.connect(slot2)
def slot1():
status = subprocess.run("external proc") # this is blocking
...
...
return
def slot2():
status = subprocess.run("external proc") # this is blocking
...
...
return
Here is the code with the example I was mentioning in the comments:
class MainWindow(QMainWindow, ui_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
ui_MainWindow.__init__(self)
self.setupUi(self)
self.button_1.clicked.connect(lambda: self.threaded_wait(1))
self.button_5.clicked.connect(lambda: self.threaded_wait(5))
self.button_10.clicked.connect(lambda: self.threaded_wait(10))
#Start a timer that executes every 0.5 seconds
self.timer = QtCore.QBasicTimer()
self.timer.start(500, self)
#INIT variables
self.results = {}
self.done = False
def timerEvent(self, event):
#Executes every 500msec.
if self.done:
print(self.results)
self.done = False
def threaded_wait(self, time_to_wait):
self.done = False
new_thread = threading.Thread(target=self.actual_wait, args=(time_to_wait,self.sender().objectName()))
new_thread.start()
def actual_wait(self, time_to_wait: int, button_name):
print(f"Button {button_name} Pressed:\nSleeping for {int(time_to_wait)} seconds")
time_passed = 0
for i in range(0, time_to_wait):
print(int( time_to_wait - time_passed))
time.sleep(1)
time_passed = time_passed + 1
self.results[button_name] = [1,2,3,4,5]
self.done = True
print("Done!")
You can use QThread. With Qthread you can pass arguments to a function in mainWindow with signal mechanism.
Here is a source that explains how to use Qthread:
https://realpython.com/python-pyqt-qthread/
if you read the soruce it will be helpfull to you, i think. And there is a sample gui in the page, i write it down to you(you can run it):
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtWidgets import QMainWindow
import time
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
import sys
# Snip...
# Step 1: Create a worker class
#
class Worker(QObject):
finished = pyqtSignal()
progress = pyqtSignal(int)
def run(self):
"""Long-running task."""
for i in range(5):
time.sleep(1)
self.progress.emit(i + 1)
self.finished.emit()
class Window(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.clicksCount = 0
self.setupUi()
def setupUi(self):
self.setWindowTitle("Freezing GUI")
self.resize(300, 150)
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
# Create and connect widgets
self.clicksLabel = QLabel("Counting: 0 clicks", self)
self.clicksLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.stepLabel = QLabel("Long-Running Step: 0")
self.stepLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.countBtn = QPushButton("Click me!", self)
self.countBtn.clicked.connect(self.countClicks)
self.longRunningBtn = QPushButton("Long-Running Task!", self)
self.longRunningBtn.clicked.connect(self.runLongTask)
# Set the layout
layout = QVBoxLayout()
layout.addWidget(self.clicksLabel)
layout.addWidget(self.countBtn)
layout.addStretch()
layout.addWidget(self.stepLabel)
layout.addWidget(self.longRunningBtn)
self.centralWidget.setLayout(layout)
def countClicks(self):
self.clicksCount += 1
self.clicksLabel.setText(f"Counting: {self.clicksCount} clicks")
def reportProgress(self, n):
self.stepLabel.setText(f"Long-Running Step: {n}")
def runLongTask(self):
# Step 2: Create a QThread object
self.thread = 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")
)
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())
Usually what I do is have the button press run a function that launches a thread to do the work for me.
In my example I have 3 buttons. One that waits for one second, another that waits for 5, and another that waits for 10.
I connect the button slots when they are clicked to threaded_wait() and I use lambda because I want to pass that method an integer argument on how long to wait for (Waiting in this example is just fake processing time).
Then I have the method actual_wait() which is the code that is actually waiting, which is being executed by the thread. Since there is a thread running that code, the main GUI event loop exits the threaded_wait() method right after starting the thread and it is allowed to continue it's event loop
class MainWindow(QMainWindow, ui_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
ui_MainWindow.__init__(self)
self.setupUi(self)
self.button_1.clicked.connect(lambda: self.threaded_wait(1))
self.button_5.clicked.connect(lambda: self.threaded_wait(5))
self.button_10.clicked.connect(lambda: self.threaded_wait(10))
def threaded_wait(self, time_to_wait):
new_thread = threading.Thread(target=self.actual_wait, args=(time_to_wait,))
new_thread.start()
def actual_wait(self, time_to_wait: int):
print(f"Sleeping for {int(time_to_wait)} seconds")
time_passed = 0
for i in range(0, time_to_wait):
print(int( time_to_wait - time_passed))
time.sleep(1)
time_passed = time_passed + 1
print("Done!")
This prevents my GUI from freezing up.
EDIT:
Sorry as for the second part of your question, if you want to wait for the thread to finish before doing something else, you can use a flag like this:
def actual_wait(self, time_to_wait: int):
print(f"Sleeping for {int(time_to_wait)} seconds")
....
self.DONE = True
And check that self.DONE flag wherever you need it.
It kind of depends what you mean by wait for it to complete.
I think if you use QThread you can also emit a signal when the thread is done and connect that signal to whatever slot after that, but I haven't used QThread.
I want to use PySide2 Qtcore.Qthread because of Qtcore.Signal, but I end up with this error:
Process finished with exit code -1073740791
from PySide2.QtCore import QThread
class Thread(QThread):
def run(self):
print('task started')
k = 0
for i in range(10000):
for j in range(5000):
k += 1
print('task finished')
Thread().start()
expect to have those prints but I have this error:
Process finished with exit code -1073740791
Update:
so, why this code also throw the same error?
class Thread(QThread):
done = Signal()
def __init__(self):
super(Thread, self).__init__()
def run(self):
print('task started')
k = 0
for i in range(10000):
for j in range(5000):
k += 1
print('task finished')
self.done.emit()
class Widget(QtWidgets.QWidget):
def __init__(self):
super(Widget, self).__init__()
btn = QtWidgets.QPushButton('test', parent=self)
btn.clicked.connect(self.clicked)
btn.show()
def clicked(self):
t = Thread()
t.done.connect(self.done)
t.start()
def done(self):
print('done')
app = QtWidgets.QApplication()
window = Widget()
window.show()
sys.exit(app.exec_())
Explanation
If you run your code in a CMD/Terminal you will get the following error:
QThread: Destroyed while thread is still running
Aborted (core dumped)
And the error is caused because the thread is destroyed while it is still running since it is a local variable, on the other hand QThread needs an event loop to run
Solution
import sys
from PySide2.QtCore import QCoreApplication, QThread
class Thread(QThread):
def run(self):
print("task started")
k = 0
for i in range(10000):
for j in range(5000):
k += 1
print("task finished")
if __name__ == "__main__":
# create event loop
app = QCoreApplication(sys.argv)
th = Thread()
th.start()
th.finished.connect(QCoreApplication.quit)
sys.exit(app.exec_())
Update:
"t" is a local variable that will be eliminated after executing clicked causing the same problem as your initial code, the solution is to prevent it from being destroyed instantly and for this there are 2 options:
Make a "t" class attribute
def clicked(self):
self.t = Thread()
self.t.done.connect(self.done)
self.t.start()
Store the QThread in a container that has a longer life cycle:
class Widget(QtWidgets.QWidget):
def __init__(self):
super(Widget, self).__init__()
btn = QtWidgets.QPushButton('test', parent=self)
btn.clicked.connect(self.clicked)
self.container = []
def clicked(self):
t = Thread()
t.done.connect(self.done)
t.start()
self.container.append(t)
# ...
Pass it as a parent to "self" but for this it is necessary that Thread allow to receive so you must implement that in the constructor:
class Thread(QThread):
done = Signal()
def __init__(self, parent=None):
super(Thread, self).__init__(parent)
# ...
def clicked(self):
t = Thread(self)
t.done.connect(self.done)
t.start()
I found the solution but I don't know why I should do this. the thread should a part of the class.
self.t = Thread()
self.t.done.connect(self.done)
self.t.start()
I have a for loop in a QThread, which is started from the main GUI via a push button. When the for loop is over I would like to come back to the main thread (which is inside the Gui class) and do something else.
As far as I understood, one should use the join method to wait for the thread to be finished. In my case it seems that MyThread is never finished.
import sys
from PyQt5 import QtCore
import PyQt5.QtWidgets as QtW
from PyQt5.QtCore import QThread
class MyWindow(QtW.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('MyWindow')
self._main = QtW.QWidget()
self.setCentralWidget(self._main)
self.button = QtW.QPushButton('Do it', self)
self.button.clicked.connect(self.MyMethod)
self.layout = QtW.QGridLayout(self)
self.layout.addWidget(self.button)
self.setLayout(self.layout)
def MyMethod(self):
self.n = 5
self.loadthread = MyThread(self.n)
self.loadthread.start()
self.loadthread.join() # Will wait for the thread until it finishes its task
print('thread finished')
class MyThread(QThread):
def __init__(self, n):
QThread.__init__(self)
self.n = n
def run(self):
for i in range(self.n):
print(i)
print('cycle finished')
if __name__ == '__main__':
app = QtCore.QCoreApplication.instance() # checks if QApplication already exists
if app is None: # create QApplication if it doesnt exist
app = QtW.QApplication(sys.argv)
mainGui = MyWindow()
mainGui.show()
app.aboutToQuit.connect(app.deleteLater)
app.exec_()
The output of the cose is
0
1
2
3
4
cycle finished
and print('thread finished') is never reached.
QThread does not have the join() method, so your application should quit unexpectedly and point to the following error message.
QLayout: Attempting to add QLayout "" to MyWindow "", which already has a layout
QWidget::setLayout: Attempting to set QLayout "" on MyWindow "", which already has a layout
0
1
2
3
Traceback (most recent call last):
File "main.py", line 24, in MyMethod
self.loadthread.join() # Will wait for the thread until it finishes its task
AttributeError: 'MyThread' object has no attribute 'join'
4
cycle finished
Aborted (core dumped)
If you want to execute some task after it is finished executing the thread you must use the finished signal of QThread:
import sys
from PyQt5 import QtCore, QtWidgets
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('MyWindow')
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
self.button = QtWidgets.QPushButton('Do it')
self.button.clicked.connect(self.my_method)
layout = QtWidgets.QGridLayout(self._main)
layout.addWidget(self.button)
layout.addWidget(self.button)
#QtCore.pyqtSlot()
def my_method(self):
self.n = 5
self.loadthread = MyThread(self.n, self)
self.loadthread.finished.connect(self.on_finished)
self.loadthread.start()
#QtCore.pyqtSlot()
def on_finished(self):
print('thread finished')
class MyThread(QtCore.QThread):
def __init__(self, n, parent=None):
QtCore.QThread.__init__(self, parent)
self.n = n
def run(self):
for i in range(self.n):
print(i)
print('cycle finished')
if __name__ == '__main__':
app = QtWidgets.QApplication.instance()
if app is None:
app = QtWidgets.QApplication(sys.argv)
mainGui = MyWindow()
mainGui.show()
app.aboutToQuit.connect(app.deleteLater)
sys.exit(app.exec_())
I'm trying to update the text in a Qt GUI object via a QThread in PyQt but I just get the error QPixmap: It is not safe to use pixmaps outside the GUI thread, then it crashes. I would really appreciate any help, thanks.
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent = None):
QMainWindow.__init__(self, parent)
self.setupUi(self)
self.output = Output()
def __del__ (self):
self.ui = None
#pyqtSignature("")
def on_goBtn_released(self):
threadnum = 1
#start threads
for x in xrange(threadnum):
thread = TheThread()
thread.start()
class Output(QWidget, Ui_Output):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.setupUi(self)
self.ui = Ui_Output
self.show()
def main(self):
self.textBrowser.append("sdgsdgsgsg dsgdsg dsgds gsdf")
class TheThread(QtCore.QThread):
trigger = pyqtSignal()
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
self.trigger.connect(Output().main())
self.trigger.emit()
self.trigger.connect(Output().main())
This line is problematic. You are instantiating a class in the thread which looks like a widget. This is wrong. You shouldn't use GUI elements in a different thread. All GUI related code should run in the same thread with the event loop.
The above line is also wrong in terms of design. You emit a custom signal from your thread and this is a good way. But the object to process this signal should be the one that owns/creates the thread, namely your MainWindow
You also don't keep a reference to your thread instance. You create it in a method, but it is local. So it'll be garbage collected, you probably would see a warning that it is deleted before it is finished.
Here is a minimal working example:
import sys
from PyQt4 import QtGui, QtCore
import time
import random
class MyThread(QtCore.QThread):
trigger = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(MyThread, self).__init__(parent)
def setup(self, thread_no):
self.thread_no = thread_no
def run(self):
time.sleep(random.random()*5) # random sleep to imitate working
self.trigger.emit(self.thread_no)
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.text_area = QtGui.QTextBrowser()
self.thread_button = QtGui.QPushButton('Start threads')
self.thread_button.clicked.connect(self.start_threads)
central_widget = QtGui.QWidget()
central_layout = QtGui.QHBoxLayout()
central_layout.addWidget(self.text_area)
central_layout.addWidget(self.thread_button)
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)
def start_threads(self):
self.threads = [] # this will keep a reference to threads
for i in range(10):
thread = MyThread(self) # create a thread
thread.trigger.connect(self.update_text) # connect to it's signal
thread.setup(i) # just setting up a parameter
thread.start() # start the thread
self.threads.append(thread) # keep a reference
def update_text(self, thread_no):
self.text_area.append('thread # %d finished' % thread_no)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mainwindow = Main()
mainwindow.show()
sys.exit(app.exec_())