posted on: 2010-01-11 20:55:31
This is a demonstration to connect a thread such that its slot is executed in a separate thread. The program has three different connection techniques.

>The goal of this is to have a QThread start its event loop and then execute slots from that event loop. Ill include a sample program that demonstrates the differences in connection types.

Ill also include a minimal example that involves some psuedo code.

#!/usr/bin/env python
from PyQt4 import QtCore,QtGui
import sys, time

class DisplayWindow(QtGui.QMainWindow):
    
    
    progress = QtCore.pyqtSignal(['int'])
    
    
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        
        self.progress.connect(self.showID)
        
        self.label = QtGui.QLabel("0")
        
        #connected to the thread itself.
        buttonA = QtGui.QPushButton("Direct")
        
        #connected to the object created during thread exec.
        buttonB = QtGui.QPushButton("Connected")
        
        #connected to a local function
        buttonC = QtGui.QPushButton("No Thread")
        
        
        self.buttonB = buttonB
        
        
        self.proc = ConnectedThread(self)
        
        #Connect buttonB after the thread has started
        self.proc.started.connect(self.connectButtonB)
        
        self.proc.start()
        
        buttonA.clicked.connect(
                        self.proc.stopNWait, 
                        type=QtCore.Qt.QueuedConnection
                        )
        
        buttonC.clicked.connect(self.sleepNoThread)
        
        #layout
        
        widge = QtGui.QWidget()
        
        layout = QtGui.QVBoxLayout()
        
        button_layout = QtGui.QHBoxLayout()
        
        button_layout.addWidget(buttonA)
        button_layout.addWidget(buttonB)
        button_layout.addWidget(buttonC)
        
        layout.addWidget(self.label)
        layout.addLayout(button_layout)
        
        widge.setLayout(layout)
        self.setCentralWidget(widge)
            
    @QtCore.pyqtSlot()
    def sleepNoThread(self):
        self.progress.emit(QtCore.QThread.currentThreadId())
        QtCore.QThread.sleep(1)
        
    @QtCore.pyqtSlot()
    def xFinished(self):
        print QtCore.QThread.currentThreadId()
        self.ok = True
    @QtCore.pyqtSlot()
    def connectButtonB(self):
        self.proc.queuedConnect(self.buttonB)
    @QtCore.pyqtSlot("int")
    def showID(self, s):
        self.label.setText("id: %s"%s);
    def cleanUp(self):
        self.proc.quit()
        self.proc.wait()
        
class ConnectedThread(QtCore.QThread):
    """
    
        This is a simple thread class to show how to connect
        a signal across threads.  The key is creating a new
        object during the QThreads event loop
    
    """

    def __init__(self, parent=None):
        QtCore.QThread.__init__(self,parent)
        self.p = parent
    def run(self):
        self.o = Starter()
        self.o.progress.connect(self.p.showID)
        self.o.progress.emit(QtCore.QThread.currentThreadId())
        self.exec_()
    
    @QtCore.pyqtSlot()
    def stopNWait(self):
        self.o.stopNWait()
    def queuedConnect(self,button):
        button.clicked.connect(
                self.o.stopNWait, 
                type=QtCore.Qt.QueuedConnection )
    
        

class Starter(QtCore.QObject):
    progress = QtCore.pyqtSignal(['int'])
    """
        This object will execute stop and wait in the thread it was 
        created in, provided a QueuedConnection is used
    """
    @QtCore.pyqtSlot()
    def stopNWait(self):
        self.progress.emit(QtCore.QThread.currentThreadId())
        QtCore.QThread.sleep(1)
        
if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    wid = DisplayWindow()
    wid.show()
    try:
        sys.exit(app.exec_())
    finally:
        wid.cleanUp()

Things to note:

* I do not connect to the thread, but the object that has been created in the threads event loop. The 'buttonB' starts performs the action in the same id thread that the Thread's 'run' method performs.

* buttonA is executed in the same event loop as buttonC and yet the behaviors are slightly different.

* I have created a similar program to this in c++ and it works the same way you need to create a new object for the slot to be called from the threads event loop.

* There is a race condition if you start the thread and then immediately try to connect to the object that you are creating. It is better to connect to the QThreads .started() signal, or to connect in the run method itself.

* The 'progress' signal is evaluated in the main thread so it is still 'safe'.


class ConnectedThread(QtCore.QThread):
    """
    
        This is an example that doesn't doe anything,
        but it shows how to connect a signal to a slot
        so that the slot will be executed in a separate
        thread.  Note "Starter" cannot be a QWidget
        as per the documentation.
    
    """

    def __init__(self, parent=None):
        QtCore.QThread.__init__(self,pysignal)
        self.pysignal = pysignal
    def run(self):
        self.o = Starter()
        self.pysignal.connect(o.performAction)
        self.exec_()
    
    def queuedConnect(self,pysignal):
        pysignal.connect(
                self.o.performAction, 
                type=QtCore.Qt.QueuedConnection )
    
        

class Starter(QtCore.QObject):
    progress = QtCore.pyqtSignal(['int'])
    """
        Dummy object for connecting in a separate event loop
    """
    @QtCore.pyqtSlot()
    def performAction(self):
        pass

Comments

matt
2010-01-12 06:56:15
On linux there is a small difference between the connected thread and the direct thread. On windows there is no appreciable difference. They both do not update the Label until after execution has completed.
Name: