Search code examples
pythonpyqtpyqt4qthread

Why the QThread behave different if create it as local variable


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()

Solution

  • 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_())