user3660964
user3660964

Reputation: 13

Qthread not working and GUI still hangs up

I am trying to implementation a basic example for threading with pyqt, where there's a textbox that gets updated regularly while processing some code. I tried to remove any unnecessary dependency and abstract the code as mush as possible, so I simply replaced the code to be processed in the background with a while loop that sends data with each cycle to be read by the textbox. however, it's not working and I have no idea what is causing the UI to hang up. I added debugging statements for the current thread id and both the main and the worker class (in my example it's called Process) do match

#!/zin/tools/bin/python
#vim:expandtab filetype=python nocindent sw=4

import sys, traceback
import os
import subprocess
import time 
from PyQt4 import QtCore,QtGui


try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

class Process (QtCore.QObject):
    processCmdDone = QtCore.pyqtSignal()
    processdataReady = QtCore.pyqtSignal(str)

    def __init__(self):
        super(Process, self).__init__()
        self.processdataReady.connect(self.debug)

    def debug(self):
        print  "signal process data ready is invoked from within the process"

    @QtCore.pyqtSlot()
    @QtCore.pyqtSlot(str)
    def execCmd(self):
        print 'worker thread id :'+ str (QtCore.QThread.currentThreadId())
        x = 0
        while x < 100:
            x+=1
            self.processdataReady.emit(str(x)+'\n')
            time.sleep(1)
        self.processCmdDone.emit()
        print "process ended"


class MainWindow (QtGui.QWidget):
    def __init__(self,filename = None, parent=None):
        super(MainWindow,self).__init__(parent)
        self.transcript_textEdit = QtGui.QTextBrowser()
        font = QtGui.QFont()
            font.setPointSize(12)
            self.transcript_textEdit.setFont(font)
            self.transcript_textEdit.setObjectName(_fromUtf8("transcript_textEdit"))
            self.layout = QtGui.QVBoxLayout(self)
            self.layout.addWidget(self.transcript_textEdit)

        self.run_pushButton = QtGui.QPushButton()
            self.run_pushButton.setFont(font)
            self.run_pushButton.setObjectName(_fromUtf8("run_pushButton"))
            self.run_pushButton.setText('Run')
            self.layout.addWidget(self.run_pushButton)
            self.run_pushButton.clicked.connect(self.execCmdThreading)

    def dataReady(self,text):

        cursor = self.transcript_textEdit.textCursor()
        cursor.movePosition(cursor.End)
        cursor.insertText(str(text))
        self.transcript_textEdit.ensureCursorVisible()


    def execCmdThreading(self):
        print 'gui thread id :'+ str (QtCore.QThread.currentThreadId())
        thread = QtCore.QThread(self)
        process_inst = Process()
        process_inst.moveToThread(thread)
        process_inst.processdataReady.connect(self.dataReady)
        process_inst.processCmdDone.connect(thread.quit)
        thread.finished.connect(thread.deleteLater)
        process_inst.processCmdDone.connect(process_inst.deleteLater)
        thread.started.connect(lambda: process_inst.execCmd())
        thread.start()



if __name__=="__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    MainWindowInst = MainWindow()
    MainWindowInst.show()
    sys.exit(app.exec_())

I am simply creating a textbox and a pushbutton, where on clicking the pushbutton a thread is created for inst of class ‘Process’, where it’s movedToThread and its method ‘execCmd’ gets executed. While executing the execCmd method, a signal is emitted ‘processdataReady’ that is expected to display text in the textbox created. However the code doesn’t seem to run as expected and the UI does hang up.

I really do appreciate anyone's help here.

Upvotes: 0

Views: 864

Answers (1)

mata
mata

Reputation: 69042

You're connecting the thread.started signal to a lambda:

thread.started.connect(lambda: process_inst.execCmd())

Using a nomal python callable here will always cause the signal to be processed in the gui thread. That's understandable beacause the callable (lambda) doesn't have a thread affinity as QObjects have. From within the lambda the process_inst.execCmd method is then executed synchronously, it doesn't matter what thread affinity the object has. Therefore the GUI thread will block.

If you want the signal to be received and processed within the worker threads event lopp, connect it to the slot directly.

For this to work, you also need to make sure that you keep a reference to process_inst, otherwise it will be destroyed when it goes out of scope.

With these adjustments your program works for me:

...
thread.started.connect(process_inst.execCmd)
thread.process_inst = process_inst
...

Upvotes: 1

Related Questions