Search code examples
pythonmultithreadingpyqtsignals-slots

Custom signals and multithreading


I've been trying to make this code work, but I still can't see where the flaw is. I'm trying to emit the signal from a new thread, so the main receives the signal and executes a function.

If I try to do it within the same thread, everything works fine - but with this code, the thread is created, but the signal is never connected.

from PyQt4.QtCore import * 
from PyQt4.QtGui import *
from PyQt4 import QtCore

class WorkThread(QtCore.QThread):
    def __init__(self):
        QtCore.QThread.__init__(self)

    def run(self):
        print("From thread")
        self.emit(QtCore.SIGNAL("trying"))
        return

class Foo(QObject):            
    def handle_trigger(self):
        print ("trigger signal received")

    def new_thread(self):
        self.get_thread = WorkThread()
        self.connect(self.get_thread, QtCore.SIGNAL("trying"), self.handle_trigger)
        self.get_thread.start()


a = Foo()
a.new_thread()

Solution

  • Edited based on comments.

    There is one main problem with your code. You're not actually starting the Qt application, so there is no main event loop running. You must add the following lines:

    app = QApplication(sys.argv)
    app.exec_()
    

    Also, you should use the new-style Qt signals/slots if possible, or if you stick with the old-style, know that the Qt Signal should be in the form of a C function if you want it to also work with PySide. To change this to work with PySide, it would be QtCore.SIGNAL("trying()"), not QtCore.SIGNAL("trying"). See the comments (specifically mine and @ekhumoro's comments) for details.

    Here's a working version of your code (using the old-style signals/slots), I tried to change the least amount possible so that you could see the small changes. I had to use PySide instead, but it should work with PyQt as well:

    from PySide.QtCore import * 
    from PySide.QtGui import *
    from PySide import QtCore
    import sys
    
    class WorkThread(QtCore.QThread):
        def __init__(self):
            QtCore.QThread.__init__(self)
    
        def run(self):
            print("From thread")
            self.emit(QtCore.SIGNAL("trying()"))
    
    class Foo(QObject):
        def handle_trigger(self):
            print ("trigger signal received")
            self.get_thread.quit()
            self.get_thread.wait()
            QApplication.quit()
    
        def new_thread(self):
            self.get_thread = WorkThread()
            self.connect(self.get_thread, QtCore.SIGNAL("trying()"), self.handle_trigger)
            self.get_thread.start()
    
    a = Foo()
    a.new_thread()
    
    app = QApplication(sys.argv)
    app.exec_()
    

    And here's a version using the new signal/slot style (see @three_pineapples comment). This is the recommended way to implement signals/slots in PyQt/PySide.

    from PySide.QtCore import * 
    from PySide.QtGui import *
    from PySide import QtCore
    import sys
    
    class WorkThread(QtCore.QThread):
        ranThread = QtCore.Signal()
        # for PyQt, use QtCore.pyqtSignal() instead
    
        def __init__(self):
            QtCore.QThread.__init__(self)
    
        def run(self):
            print("From thread")
            self.ranThread.emit()
    
    class Foo(QObject):
        def handle_trigger(self):
            print ("trigger signal received")
            self.get_thread.quit()
            self.get_thread.wait()
            QApplication.quit()
    
        def new_thread(self):
            self.get_thread = WorkThread()
            self.get_thread.ranThread.connect(self.handle_trigger)
            self.get_thread.start()
    
    a = Foo()
    a.new_thread()
    
    app = QApplication(sys.argv)
    app.exec_()