Cannot get qthread example to work - python

I am totally stuck with this python qt threading tutorial. I have two issues.
1) The longRunning function never executes.
2) When exiting the app, I get QThread: Destroyed while thread is still running and a Python.exe has stopped working.
Any help? Thanks!!
#!/usr/bin/env python2
import sys, time
from PySide.QtGui import *
from PySide import QtCore
class SeperateThread(QtCore.QObject):
finished = QtCore.Signal()
def __init__(self, parent = None):
super(SeperateThread, self).__init__(parent)
self._isRunning = True
def longRunning(self):
end = time.time()+3
while self._isRunning == True:
sys.stdout.write('*')
sys.stdout.flush()
time.sleep(1)
now = time.time()
if now>=end:
self._isRunning=False
self.finished.emit()
def stop(self):
self._isRunning = False
class MainWindow(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self,parent)
centralwidget = QWidget(self)
startButton = QPushButton('Start long (3 seconds) operation',self)
label2 = QLabel('Long batch')
vbox = QVBoxLayout()
vbox.addWidget(startButton)
vbox.addWidget(label2)
self.setCentralWidget(centralwidget)
centralwidget.setLayout(vbox)
obj = SeperateThread()
objThread = QtCore.QThread()
obj.moveToThread(objThread)
objThread.started.connect(obj.longRunning)
def LongOperationStart():
label2.setText('app is running')
startButton.setEnabled(False)
objThread.start()
def LongOperationFinish():
startButton.setEnabled(True)
#GUI start button
startButton.clicked.connect(LongOperationStart)
#Once thread is finished.
objThread.finished.connect(LongOperationFinish)
obj.finished.connect(objThread.quit)
if __name__=='__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

Oh ok, I found out what I was doing wrong:
SeperateThread is being garbage collected after the MainWindow constructor returns. Make obj a member of MainWindow so that it persists beyond the constructor.
Thanks!!

Related

How to get a child thread to close when main GUI window is closed in pyqt5 / python 3?

I am writing a GUI using pyqt5 (Python 3.6). I am trying to run another thread in parallel of the main GUI. I would like this child thread to terminate when I close the main application. In this example, the child thread is a simple counter. When I close the main GUI, the counter still keeps going. How can I get the thread to end when the GUI window is closed? In the real case I may have a thread that is running operations that takes a few minutes to execute. I am reluctant to use a flag within the thread to assess if it should end because it may take minutes for the thread to close after the GUI window has been closed. I would prefer the thread to end right away. Any suggestions?
Thank you.
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QApplication,QPushButton,
QVBoxLayout)
import time, threading, sys
class testScriptApp(QtWidgets.QWidget):
def __init__(self, parent=None):
# initialize th widget
QtWidgets.QWidget.__init__(self, parent)
# set the window title
self.setWindowTitle("Scripting")
# manage the layout
self.mainGrid = QVBoxLayout()
self.button = QPushButton('Start')
self.button.clicked.connect(self.on_click)
self.mainGrid.addWidget(self.button)
self.setLayout(self.mainGrid)
def on_click(self):
self.worker = threading.Thread(target=Worker)
self.worker.daemon = True
self.worker.start()
def Worker(count=1):
while count>0:
print(count)
time.sleep(2)
count+=1
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myapp = testScriptApp()
myapp.show()
app.exec_()
I tried to use the QThread but this locks up the main GUI. I am not sure if I am implementing it correctly.
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QApplication,QPushButton,
QVBoxLayout)
from PyQt5.QtCore import QThread
import time, threading, sys
class testScriptApp(QtWidgets.QWidget):
def __init__(self, parent=None):
# initialize th widget
QtWidgets.QWidget.__init__(self, parent)
# set the window title
self.setWindowTitle("Scripting")
# manage the layout
self.mainGrid = QVBoxLayout()
self.button = QPushButton('Start')
self.button.clicked.connect(self.on_click)
self.mainGrid.addWidget(self.button)
self.setLayout(self.mainGrid)
def on_click(self):
self.worker = Worker()
self.worker.run()
def closeEvent(self,event):
print('Closing')
self.worker.terminate()
event.accept()
class Worker(QThread):
def __init__(self):
QThread.__init__(self)
def run(self):
count=1
while count>0:
print(count)
time.sleep(2)
count+=1
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myapp = testScriptApp()
myapp.show()
app.exec_()
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QApplication,QPushButton,
QVBoxLayout)
from PyQt5.QtCore import QThread,QObject
import time, threading, sys
class testScriptApp(QtWidgets.QWidget):
def __init__(self, parent=None):
# initialize th widget
QtWidgets.QWidget.__init__(self, parent)
# set the window title
self.setWindowTitle("Scripting")
# manage the layout
self.mainGrid = QVBoxLayout()
self.button = QPushButton('Start')
self.button.clicked.connect(self.on_click)
self.mainGrid.addWidget(self.button)
self.setLayout(self.mainGrid)
def on_click(self):
self.my_thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.my_thread)
self.my_thread.started.connect(self.worker.run)
self.my_thread.start()
def closeEvent(self,event):
print('Closing')
class Worker(QObject):
def __init__(self):
super().__init__()
def run(self):
count=1
while count>0:
print(count)
time.sleep(2)
count+=1
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myapp = testScriptApp()
myapp.show()
app.exec_()
hope self.my_thread.requestInterruption() won't qualify as flag, otherwise is not the sought answer :
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QApplication,QPushButton,
QVBoxLayout)
from PyQt5.QtCore import QThread,QObject
import time, threading, sys
class testScriptApp(QtWidgets.QWidget):
def __init__(self, parent=None):
# initialize th widget
QtWidgets.QWidget.__init__(self, parent)
# set the window title
self.setWindowTitle("Scripting")
# manage the layout
self.mainGrid = QVBoxLayout()
self.button = QPushButton('Start')
self.button.clicked.connect(self.on_click)
self.mainGrid.addWidget(self.button)
self.setLayout(self.mainGrid)
def on_click(self):
self.my_thread = QThread()
self.worker = Worker(self.my_thread)
self.worker.moveToThread(self.my_thread)
self.my_thread.started.connect(self.worker.run)
self.my_thread.start()
def closeEvent(self,event):
self.my_thread.requestInterruption()
print('Closing')
self.my_thread.quit()
self.my_thread.wait()
print('is thread running ?', self.my_thread.isRunning())
class Worker(QObject):
def __init__(self, thread):
super().__init__()
self.thread = thread
def run(self):
count=1
while count>0:
print(count)
time.sleep(2)
count+=1
if self.thread.isInterruptionRequested():
break
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myapp = testScriptApp()
myapp.show()
app.exec_()
see PySide6.QtCore.QThread.isInterruptionRequested()
Using threading.thread like in your question, I used threading.Event() and I set it (= True) to stop your Worker loop, but once again I hope is not a flag:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QApplication,QPushButton,
QVBoxLayout)
import time, threading, sys
class testScriptApp(QtWidgets.QWidget):
def __init__(self, parent=None):
# initialize th widget
QtWidgets.QWidget.__init__(self, parent)
# set the window title
self.setWindowTitle("Scripting")
# manage the layout
self.mainGrid = QVBoxLayout()
self.button = QPushButton('Start')
self.button.clicked.connect(self.on_click)
self.mainGrid.addWidget(self.button)
self.setLayout(self.mainGrid)
self.event = threading.Event()
def on_click(self):
self.worker = threading.Thread(target=Worker, args = (self.event,))
self.worker.daemon = True
self.worker.start()
def closeEvent(self, event):
if 'worker' in self.__dict__:
self.event.set() ## sets event: threading.Event() = True
self.worker.join() ## waits self.worker to complete ***
print('is worker alive, self.worker.is_alive() : ', self.worker.is_alive())
pass
def Worker(event, count=1 ):
print(event)
while count>0:
print(count)
time.sleep(2)
count+=1
if event.is_set():
break
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myapp = testScriptApp()
myapp.show()
app.exec_()
try the code commenting and uncommentig out self.event.set() & self.worker.join() in
def closeEvent(self, event):
if 'worker' in self.__dict__:
self.event.set() ## sets event: threading.Event() = True
self.worker.join() ## waits self.worker to complete ***
print('is worker alive, self.worker.is_alive() : ', self.worker.is_alive())
pass
*** Wait until the thread terminates. This blocks the calling thread until the thread whose join() method is called terminates –

What is the proper way of opening a child dialog in a second thread in PyQt?

I have an application where I run some process in a second thread and at some point, given a certain condition, another dialog window opens, which halts the process until you confirm something. This causes the following error message:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QApplication(0x1f9c82383d0), parent's thread is QThread(0x1f9c7ade2a0), current thread is QThread(0x1f9c8358800)
Interestingly, if you also move your cursor over the MainWindow while the process is running, and before the new dialog pops up, it also produces this error message a couple of times:
QBasicTimer::stop: Failed. Possibly trying to stop from a different thread
Very strange. Because it only occurs if you move your cursor over the MainWindow.
Now, in my application, I actually load an interface for the new dialog that pops up using PyQt5.uic.loadUi, and this hasn't caused any problems. However, when I was creating the example for this post, another issue occurred, due to the fact that I was setting the layout of the new dialog during its initialization:
QObject::setParent: Cannot set parent, new parent is in a different thread
Which results in the application crashing:
Process finished with exit code -1073741819 (0xC0000005)
I'm obviously doing something wrong here regarding the threading I would guess, but I don't know what. I am especially baffled by the fact that I cannot set the layout of the new dialog during its initialization, while using loadUi is totally fine. Here is my example code:
import sys
import time
import numpy as np
from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import (
QDialog, QApplication, QPushButton, QGridLayout, QProgressBar, QLabel
)
class SpecialDialog(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton('pass variable')
btn.clicked.connect(self.accept)
layout = QGridLayout()
layout.addWidget(btn)
# self.setLayout(layout)
self.variable = np.random.randint(0, 100)
class Handler(QObject):
progress = pyqtSignal(int)
finished = pyqtSignal(int)
def __init__(self):
super().__init__()
self._isRunning = True
self._success = False
def run(self):
result = None
i = 0
while i < 100 and self._isRunning:
if i == 50:
dialog = SpecialDialog()
dialog.exec_()
result = dialog.variable
time.sleep(0.01)
i += 1
self.progress.emit(i)
if i == 100:
self._success = True
self.finished.emit(result)
def stop(self):
self._isRunning = False
class MainWindow(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton('test')
btn.clicked.connect(self.run_test)
self.pbar = QProgressBar()
self.resultLabel = QLabel('Result:')
layout = QGridLayout(self)
layout.addWidget(btn)
layout.addWidget(self.pbar)
layout.addWidget(self.resultLabel)
self.setLayout(layout)
self.handler = None
self.handler_thread = QThread()
self.result = None
def run_test(self):
self.handler = Handler()
self.handler.moveToThread(self.handler_thread)
self.handler.progress.connect(self.progress)
self.handler.finished.connect(self.finisher)
self.handler_thread.started.connect(self.handler.run)
self.handler_thread.start()
def progress(self, val):
self.pbar.setValue(val)
def finisher(self, result):
self.result = result
self.resultLabel.setText(f'Result: {result}')
self.pbar.setValue(0)
self.handler.stop()
self.handler.progress.disconnect(self.progress)
self.handler.finished.disconnect(self.finisher)
self.handler_thread.started.disconnect(self.handler.run)
self.handler_thread.terminate()
self.handler = None
if __name__ == '__main__':
app = QApplication(sys.argv)
GUI = MainWindow()
GUI.show()
sys.exit(app.exec_())
EDIT
I forgot to mention that I already found this post, which may be related to my problem, however, I don't undestand the reasoning of the solution in the top answer, and more importantly, I don't speak what I believe is C++.
You cannot create or modify a GUI element from a secondary thread and this is signaled by the error message.
You have to redesign the Handler class, with your requirement you must divide run into 2 methods, the first method will generate progress up to 50% where the GUI will open the dialogue, obtain the result and launch the second method.
import sys
import time
import numpy as np
from functools import partial
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, QTimer
from PyQt5.QtWidgets import (
QDialog,
QApplication,
QPushButton,
QGridLayout,
QProgressBar,
QLabel,
)
class SpecialDialog(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton("pass variable")
btn.clicked.connect(self.accept)
layout = QGridLayout()
layout.addWidget(btn)
# self.setLayout(layout)
self.variable = np.random.randint(0, 100)
class Handler(QObject):
progress = pyqtSignal(int)
finished = pyqtSignal(int)
def __init__(self):
super().__init__()
self._isRunning = True
self._success = False
#pyqtSlot()
def task1(self):
i = 0
while i <= 50 and self._isRunning:
time.sleep(0.01)
i += 1
self.progress.emit(i)
#pyqtSlot(int)
def task2(self, result):
i = 50
while i < 100 and self._isRunning:
time.sleep(0.01)
i += 1
self.progress.emit(i)
if i == 100:
self._success = True
self.finished.emit(result)
def stop(self):
self._isRunning = False
class MainWindow(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton("test")
btn.clicked.connect(self.run_test)
self.pbar = QProgressBar()
self.resultLabel = QLabel("Result:")
layout = QGridLayout(self)
layout.addWidget(btn)
layout.addWidget(self.pbar)
layout.addWidget(self.resultLabel)
self.setLayout(layout)
self.handler = None
self.handler_thread = QThread()
self.result = None
def run_test(self):
self.handler = Handler()
self.handler.moveToThread(self.handler_thread)
self.handler.progress.connect(self.progress)
self.handler.finished.connect(self.finisher)
self.handler_thread.started.connect(self.handler.task1)
self.handler_thread.start()
#pyqtSlot(int)
def progress(self, val):
self.pbar.setValue(val)
if val == 50:
dialog = SpecialDialog()
dialog.exec_()
result = dialog.variable
wrapper = partial(self.handler.task2, result)
QTimer.singleShot(0, wrapper)
def finisher(self, result):
self.result = result
self.resultLabel.setText(f"Result: {result}")
self.pbar.setValue(0)
self.handler.stop()
self.handler_thread.quit()
self.handler_thread.wait()
if __name__ == "__main__":
app = QApplication(sys.argv)
GUI = MainWindow()
GUI.show()
sys.exit(app.exec_())

QTimer in worker thread blocking GUI

I am trying to create a worker thread whose job is to monitor the status bit of a positioning platform.
To do this I connect a QTimer timeout signal to a function that queries the platform.
class expSignals(QtCore.QObject):
pause=QtCore.pyqtSignal()
class motorpositioner(QtCore.QObject):
def __init__(self):
QtCore.QThread.__init__(self)
self.timer = QtCore.QTimer()
self.timer.start(100)
self.timer.timeout.connect(self.do_it)
self.lock=QtCore.QMutex()
self.running=True
self.stat=0
def do_it(self):
with QtCore.QMutexLocker(self.lock):
#self.stat = self.motors.get_status()
print(self.stat)
time.sleep(5)
#QtCore.pyqtSlot()
def stop1(self):
self.timer.stop()
print('stop heard')
The GUI stuff looks like this:
class MyApp(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.thread=QtCore.QThread(self)
#worker
self.mot=motorpositioner()
# =============================================================================
# Putting buttons and GUI stuff in place
# =============================================================================
self.button=QtWidgets.QPushButton('Derp',self)
layout = QtWidgets.QHBoxLayout()
layout.addWidget(self.button)
self.setLayout(layout)
self.setGeometry( 300, 300, 350, 300 )
# =============================================================================
# Connecting signals
# =============================================================================
self.sig=expSignals()
self.sig2=expSignals()
self.button.clicked.connect(self.stop)
self.sig.pause.connect(self.mot.stop1)
self.sig2.pause.connect(self.thread.quit)
self.mot.moveToThread(self.thread)
self.thread.start()
def stop(self):
self.sig.pause.emit()
def closeEvent(self,event):
self.sig2.pause.emit()
event.accept()
However the way it is written now the GUI is unresponsive. However if I comment out self.timer.timeout.connect(self.do_it) and put do_it in a while(True) loop, the GUI isn't being blocked.
Why is the main thread being blocked when using QTimer?
I do not know what is expSignals() and I think it is not relevant, and neither is the button.
Your code has the following errors:
You are starting the timer before the thread starts, so the task will run on the GUI thread.
QTimer is not a child of motorpositioner so if motorpositioner moves to the new thread QTimer will not. For him to move he must be a son so you must pass him as a parent to self.
I do not know if it is a real error, but you are firing the QTimer every 100 ms but the task takes 5 seconds, although the QMutex helps to have no problems because it is blocked.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import time
class motorpositioner(QtCore.QObject):
def __init__(self):
QtCore.QThread.__init__(self)
self.timer = QtCore.QTimer(self)
self.lock = QtCore.QMutex()
self.running = True
self.stat = 0
def start_process(self):
self.timer.timeout.connect(self.do_it)
self.timer.start(100)
def do_it(self):
with QtCore.QMutexLocker(self.lock):
#self.stat = self.motors.get_status()
print(self.stat)
time.sleep(5)
#QtCore.pyqtSlot()
def stop1(self):
self.timer.stop()
print('stop heard')
class MyApp(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.thread = QtCore.QThread(self)
self.mot = motorpositioner()
self.mot.moveToThread(self.thread)
self.thread.started.connect(self.mot.start_process)
self.thread.start()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = MyApp()
ex.show()
sys.exit(app.exec_())

QThread can not be called in PyQt

I have implement a subclass about the QThread, but the run can not be called:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class MyThread(QThread):
def __init__(self):
super(MyThread,self).__init__()
def run(self):
for i in range(1000):
print(i)
if __name__ == '__main__':
import sys
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(500,500)
self.label = QLabel()
self.setCentralWidget(self.label)
layout = QHBoxLayout()
self.label.setLayout(layout)
btn = QPushButton('start')
layout.addWidget(btn)
btn.clicked.connect(self.BTNClick)
def BTNClick(self):
thread = MyThread()
thread.start()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
When I debug the code, I find the MyThread is normally run. But when I directly run the code, the function 'run' would not be called.
A local variable is deleted when you finish executing the function, in your case thread is a local variable of BTNClick, so when you start it is eliminated, if you want the thread to persist even after executing BTNClick you must do it an attribute using self:
def BTNClick(self):
self.thread = MyThread()
self.thread.start()

End all processes started by GUI at exit (daemon = True not working)

How can you ensure that all processes are shutdown whenever a user ends the main program UI? I have tried setting the processes to daemon = True but this is not working. I have seen other threads state that you should use a signal and continually check if a "quit" signal has been emitted, but this doesn't make sense to me in regards to where to place this script since the GUI code should no longer be executing if the GUI is now gone.
How would the process know if the GUI is gone in this case: (where sys.exit(app.exec_()) is executed)
from PyQt4 import QtCore, QtGui
import multiprocessing as mp
import sys
import time
def f(data):
for i in range(0,10**5):
data[0] += 1
time.sleep(.1)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self)
self.centralwidget = QtGui.QWidget(self)
self.top_level_layout = QtGui.QGridLayout(self.centralwidget)
self.setCentralWidget(self.centralwidget)
self.centralwidget.setLayout(self.top_level_layout)
self.process_button = QtGui.QPushButton("Start Process")
self.top_level_layout.addWidget(self.process_button, 0, 0)
QtCore.QObject.connect(self.process_button, QtCore.SIGNAL("clicked()"), self.start_process)
self.processlist = list()
self.manager = mp.Manager()
self.data = self.manager.list([1])
for i in range(0,10):
self.processlist.append(mp.Process(target=f, args=(self.data,)))
self.processlist[-1].daemon = True
def start_process(self):
for p in self.processlist:
p.start()
if __name__ == "__main__":
app = QtGui.QApplication([])
win = MainWindow()
win.show()
sys.exit(app.exec_())
Found the answer. You can redefine a closeEvent function in a QWidget object. Like so:
def closeEvent(self, event):
for p in self.processlist:
p.terminate()
Here is an updated code snippet:
from PyQt4 import QtCore, QtGui
import multiprocessing as mp
import sys
import time
def f(data):
for i in range(0,10**5):
data[0] += 1
time.sleep(.1)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self)
self.centralwidget = QtGui.QWidget(self)
self.top_level_layout = QtGui.QGridLayout(self.centralwidget)
self.setCentralWidget(self.centralwidget)
self.centralwidget.setLayout(self.top_level_layout)
self.process_button = QtGui.QPushButton("Start Process")
self.top_level_layout.addWidget(self.process_button, 0, 0)
QtCore.QObject.connect(self.process_button, QtCore.SIGNAL("clicked()"), self.start_process)
self.processlist = list()
self.manager = mp.Manager()
self.data = self.manager.list([1])
for i in range(0, 10):
self.processlist.append(mp.Process(target=f, args=(self.data,)))
self.processlist[-1].daemon = True
def closeEvent(self, event):
for p in self.processlist:
p.terminate()
def start_process(self):
for p in self.processlist:
p.start()
if __name__ == "__main__":
app = QtGui.QApplication([])
win = MainWindow()
win.show()
sys.exit(app.exec_())

Categories