I have found a strange behavior if I create QThread as local variable.
For example, the following code will work as single thread, which means I need to wait 10 seconds and the result will come out.
But if I change thread from local variable to member variable, it works as multi thread.
How's it coming? Could anyone give me some tips?
class UI():
def __init__(self):
self.app = QtGui.QApplication(sys.argv)
self.dialog = QtGui.QDialog()
self.ui = Ui_Dialog()
self.ui.setupUi(self.dialog)
self.ui.btn.clicked.connect(self.btnclick)
def run(self):
self.dialog.show()
sys.exit(self.app.exec_())
def btnclick(self):
## if change to self.thread, the behavior changes!!
signal = QtCore.SIGNAL("Log(QString)")
thread = testThread(signal)
QtCore.QObject.connect(thread, signal, self.output)
thread.start()
def output(self, txt):
self.ui.logText.append(str(txt))
class testThread(QThread):
def __init__(self, signal):
QThread.__init__(self)
self.signal = signal
def __del__(self):
self.wait()
def run(self):
for i in range(10):
time.sleep(1)
self.output(str(i))
def output(self, txt):
self.emit(self.signal, txt)
if __name__ == "__main__":
ui = UI()
ui.run()
The problem as to point out is that it is a local variable that will be destroyed a moment after having started the QThread so the thread that is handled by QThread(QThread is not a thread, it is a thread handler) will be removed and when using wait() it is expected that run() method will be executed but in the main thread generating the GUI s freeze.
So the solution is to extend the life of the variable thread, one way you point out that it works: make it a member of the class, but there is another way that only works with QObjects as QThread and that is to pass a parent (the parent must to be another QObject) that will extend the life of the object to the same capacity of the parent, for that reason I will use a dialog.
Finally nowadays it is not recommended to create signals dynamically, it is better to create it as part of a class, also for the connection you must use the new syntax.
class UI():
def __init__(self):
self.app = QtGui.QApplication(sys.argv)
self.dialog = QtGui.QDialog()
self.ui = Ui_Dialog()
self.ui.setupUi(self.dialog)
self.ui.btn.clicked.connect(self.btnclick)
self.dialog.show()
def btnclick(self):
thread = testThread(self.dialog)
thread.signal.connect(self.output)
thread.start()
def output(self, txt):
self.ui.logText.append(str(txt))
class testThread(QtCore.QThread):
signal = QtCore.pyqtSignal(str)
def __del__(self):
self.wait()
def run(self):
for i in range(10):
QtCore.QThread.sleep(1)
self.output(str(i))
def output(self, txt):
self.signal.emit(txt)
if __name__ == '__main__':
ui = UI()
app = QtGui.QApplication.instance()
if app is not None:
sys.exit(app.exec_())
Related
I'm trying to implement a webcam using PyQt5 from an example I found (here, but not really relevant).
Getting the example to work wasn't an issue, but I wanted to modify some things and I am stuck on one particular problem.
I have two classes, one QObject Capture which has a QBasicTimer that I want to start, and a QWidget MyWidget with a button that is supposed to start the timer of the Capture object, which is inside a QThread.
If I directly connect the button click to the method that starts the timer, everything works fine.
But I want to do some other things when I click the button, so I connected the button to a method of MyWidget first and call the start method of Capture from there. This, however, doesn't work: the timer doesn't start.
Here is a minimal working example:
from PyQt5 import QtCore, QtWidgets
import sys
class Capture(QtCore.QObject):
def __init__(self, parent=None):
super(Capture, self).__init__(parent)
self.m_timer = QtCore.QBasicTimer()
def start(self):
print("capture start called")
self.m_timer.start(1000, self)
def timerEvent(self, event):
print("time event")
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
self.btn_start = QtWidgets.QPushButton("Start")
lay.addWidget(self.btn_start)
self.capture = Capture()
captureThread = QtCore.QThread(self)
captureThread.start()
self.capture.moveToThread(captureThread)
# self.btn_start.clicked.connect(self.capture.start) # this works
self.btn_start.clicked.connect(self.startCapture) # this doesn't
# self.capture.start() # this doesn't either
self.show()
def startCapture(self):
self.capture.start()
def run_app():
app = QtWidgets.QApplication(sys.argv)
mainWin = MyWidget()
mainWin.show()
app.exec_()
run_app()
It is some problem with the QThread, because if I don't use threading it works. I thought maybe it has something to do with the thread not being in some way available when called from a different method than the one it was created in, but calling self.capture.start() directly from the init does not work either.
I only have a very basic grasp of threads. Can someone tell me how I can properly call self.capture.start() from MyWidget and why it works without problems when directly connecting it to the button click?
If you connect the button's clicked signal to the worker's start slot, Qt will automatically detect that it's a cross-thread connection. When the signal is eventually emitted, it will be queued in the receiving thread's event-queue, which ensures the slot will be called within the worker thread.
However, if you connect the button's clicked signal to the startCapture slot, there's no cross-thread connection, because the slot belongs to MyWidget (which lives in the main thread). When the signal is emitted this time, the slot tries to create the timer from within the main thread, which is not supported. Timers must always be started within the thread that creates them (otherwise Qt will print a message like "QBasicTimer::start: Timers cannot be started from another thread").
A better approach is to connect the started and finished signals of the thread to some start and stop slots in the worker, and then call the thread's start and quit methods to control the worker. Here's a demo based on your script, which shows how to implement that:
from PyQt5 import QtCore, QtWidgets
import sys
class Capture(QtCore.QObject):
def __init__(self, parent=None):
super(Capture, self).__init__(parent)
self.m_timer = QtCore.QBasicTimer()
def start(self):
print("capture start called")
self.m_timer.start(1000, self)
def stop(self):
print("capture stop called")
self.m_timer.stop()
def timerEvent(self, event):
print("time event")
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
self.btn_start = QtWidgets.QPushButton("Start")
lay.addWidget(self.btn_start)
self.capture = Capture()
self.captureThread = QtCore.QThread(self)
self.capture.moveToThread(self.captureThread)
self.captureThread.started.connect(self.capture.start)
self.captureThread.finished.connect(self.capture.stop)
self.btn_start.clicked.connect(self.startCapture)
self.show()
def startCapture(self):
if not self.captureThread.isRunning():
self.btn_start.setText('Stop')
self.captureThread.start()
else:
self.btn_start.setText('Start')
self.stopCapture()
def stopCapture(self):
self.captureThread.quit()
self.captureThread.wait()
def closeEvent(self, event):
self.stopCapture()
def run_app():
app = QtWidgets.QApplication(sys.argv)
mainWin = MyWidget()
mainWin.show()
app.exec_()
run_app()
I create a button and try to run multiprocessing when I click button ,
but the UI is become blocked . I hope process run in backgorund .
How can I fix it ?
from PySide2 import QtCore,QtGui,QtWidgets
import sys
import multiprocessing
from threading import Timer
class TTT(multiprocessing.Process):
def __init__(self):
super(TTT, self).__init__()
self.daemon = True
def run(self):
while True:
t = Timer(5, self.doSomething)
t.start()
t.join()
def doSomething(self):
try:
print('123')
except Exception as e:
print(e)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
btn = QtWidgets.QPushButton('run process')
btn.clicked.connect(self.create_process)
self.setCentralWidget(btn)
def create_process(self):
QtWidgets.QMessageBox.information(self,'hhh','hhh')
t = TTT()
t.start()
t.join()
if __name__=="__main__":
app=QtWidgets.QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
Bendegúz Szatmári already answer the main question.
I just want to let you know that use Process is not best idea in most of usage.
Different process does not share memory with your program. You can not control them so easily as different thread.
Here is simple example how you can Start end Stop different thread.
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
import sys
import time
class TTT(QThread):
def __init__(self):
super(TTT, self).__init__()
self.quit_flag = False
def run(self):
while True:
if not self.quit_flag:
self.doSomething()
time.sleep(1)
else:
break
self.quit()
self.wait()
def doSomething(self):
print('123')
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.btn = QtWidgets.QPushButton('run process')
self.btn.clicked.connect(self.create_process)
self.setCentralWidget(self.btn)
def create_process(self):
if self.btn.text() == "run process":
print("Started")
self.btn.setText("stop process")
self.t = TTT()
self.t.start()
else:
self.t.quit_flag = True
print("Stop sent")
self.t.wait()
print("Stopped")
self.btn.setText("run process")
if __name__=="__main__":
app=QtWidgets.QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
I have never used multiprocessing, but docs says that the join() method blocks the caller until it is finished. Putting the method in an infinite loop will block the caller(the UI) forever.
Here's a nice strategy:
https://elsampsa.github.io/valkka-examples/_build/html/qt_notes.html#python-multiprocessing
Features:
You can send Qt signals from the multiprocess
You can send Qt signals into the multiprocess
Problem Description
I'm trying to make an application that collects data, processes it, displays it, and some actuation (open/close valves, etc). As a practice for future applications where I have some stricter time constraints, I want to run the data capture and processing in a separate thread.
My current problem is that it's telling me I cannot start a timer from another thread.
Current code progress
import sys
import PyQt5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QThread, pyqtSignal
# This is our window from QtCreator
import mainwindow_auto
#thread to capture the process data
class DataCaptureThread(QThread):
def collectProcessData():
print ("Collecting Process Data")
#declaring the timer
dataCollectionTimer = PyQt5.QtCore.QTimer()
dataCollectionTimer.timeout.connect(collectProcessData)
def __init__(self):
QThread.__init__(self)
def run(self):
self.dataCollectionTimer.start(1000);
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self) # gets defined in the UI file
self.btnStart.clicked.connect(self.pressedStartBtn)
self.btnStop.clicked.connect(self.pressedStopBtn)
def pressedStartBtn(self):
self.lblAction.setText("STARTED")
self.dataCollectionThread = DataCaptureThread()
self.dataCollectionThread.start()
def pressedStopBtn(self):
self.lblAction.setText("STOPPED")
self.dataCollectionThread.terminate()
def main():
# a new app instance
app = QApplication(sys.argv)
form = MainWindow()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Any advice on how to get this to work would be appreciated!
You have to move the QTimer to the DataCaptureThread thread, in addition to that when the run method ends, the thread is eliminated so the timer is eliminated, so you must avoid running that function without blocking other tasks. QEventLoop is used for this:
class DataCaptureThread(QThread):
def collectProcessData(self):
print ("Collecting Process Data")
def __init__(self, *args, **kwargs):
QThread.__init__(self, *args, **kwargs)
self.dataCollectionTimer = QTimer()
self.dataCollectionTimer.moveToThread(self)
self.dataCollectionTimer.timeout.connect(self.collectProcessData)
def run(self):
self.dataCollectionTimer.start(1000)
loop = QEventLoop()
loop.exec_()
I am running into an issue when I am taking in values over serial and then attempting to update my Gui with those values. Unfortunately, even though the values update correctly, I am unable to get to the screen to refresh unless I click off of it and then back on to it. I have tried repaint, update, and processEvents() but have been unable to solve the problem.
Here is the code I am working with:
import sys
import serial
import time
import requests
import PyQt5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import*
from PyQt5.QtGui import *
import mainwindow_auto
CUSTOM_EVENT = 1000
ser = serial.Serial('/dev/ttyACM0', 9600)
class TestThread(QThread):
def __init__(self, target):
QThread.__init__(self)
self.target = target
def run(self):
while True:
QApplication.postEvent(self.target, QEvent(QEvent.Type(CUSTOM_EVENT)))
QApplication.processEvents()
QThread.sleep(15)
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)# gets defined in the UI file
self.thread = TestThread(self)
self.thread.start()
def event(s, e):
if(e.type() == CUSTOM_EVENT):
print("Readline: ",int(ser.readline()))
SOC = int(ser.readline())
s.lcdNumber.display(SOC)
s.progressBar.setValue(SOC)
print("SOC: ",SOC)
print(s.lcdNumber.value())
return True
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.lcdNumber.display(30)
form.progressBar.setValue(30)
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Thanks in advance!
Since you already have an I/O thread, let it handle the I/O and sent the received value to the main thread via a signal.
No need for a custom event, no I/O on the main thread.
Just adding a signal to the thread subclass and connecting a slot to that before starting the thread.
Rather than rewriting the code that I had above, I ended up fixing it by force redrawing using s.hide() and s.show() after updating the values in the event code. It forced a redraw that otherwise refused to work.
s.lcdNumber.display(SOC)
s.progressBar.setValue(SOC)
s.hide()
s.show()
As suggested by #KevinKrammer, this is simple to do with a custom signal:
class TestThread(QThread):
serialUpdate = pyqtSignal(int)
def run(self):
while True:
QThread.sleep(1)
value = int(ser.readline())
self.serialUpdate.emit(value)
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
self.thread = TestThread(self)
self.thread.serialUpdate.connect(self.handleSerialUpdate)
self.thread.start()
def handleSerialUpdate(self, value):
print("Readline: ", value)
self.lcdNumber.display(value)
self.progressBar.setValue(value)
I'm having problem with QThreads in python. I want to change background color of label.
But My application crash while starting.
"QThread: Destroyed while thread is still running"
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
statusTh = statusThread(self)
self.connect(statusTh, SIGNAL('setStatus'), self.st, Qt.QueuedConnection)
statusTh.start()
def st(self):
if self.status == 'ON':
self.ui.label.setStyleSheet('background-color:green')
else:
self.ui.label.setStyleSheet('background-color:red')
class statusThread(QThread):
def __init__(self, mw):
super(statusThread, self).__init__()
def run(self):
while True:
time.sleep(1)
self.emit(SIGNAL('setStatus'))
if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
You're not storing a reference to the thread after it's been created, which means that it will be garbage collected (ie. destroyed) some time after the program leaves MainWindows __init__. You need to store it at least as long as the thread is running, for example use self.statusTh:
self.statusTh = statusThread(self)
self.connect(self.statusTh, SIGNAL('setStatus'), self.st, Qt.QueuedConnection)
self.statusTh.start()
I know it's quite necroposting but this may be useful. In the main section of your script, a first level customized widget need to be stored in variable, not only created.
For example I have a custom widget class called MainWindow that create a QThread. My main is like that:
from myPackage import MainWindow
if __name__ == "__main__":
app = QApplication([])
widget=MainWindow()
sys.exit(app.exec())
if I avoid the widged = definition and only call for MainWindow(), my script will crash with QThread: Destroyed while thread is still running