PyQt: Connecting a signal to a slot to start a background operation

It shouldn't matter whether the connection is made before or after moving the worker object to the other thread. To quote from the Qt docs:

Qt::AutoConnection - If the signal is emitted from a different thread than the receiving object, the signal is queued, behaving as Qt::QueuedConnection. Otherwise, the slot is invoked directly, behaving as Qt::DirectConnection. The type of connection is determined when the signal is emitted. [emphasis added]

So, as long as the type argument of connect is set to QtCore.Qt.AutoConnection (which is the default), Qt should ensure that signals are emitted in the appropriate way.

The problem with the example code is more likely to be with the slot than the signal. The python method that the signal is connected to probably needs to be marked as a Qt slot, using the pyqtSlot decorator:

from QtCore import pyqtSlot

class Scanner(QObject):
    
    @pyqtSlot()
    def scan(self):
        scan_value(start, stop, step)
        progress.setValue(100)

EDIT:

It should be clarified that it's only in fairly recent versions of Qt that the type of connection is determined when the signal is emitted. This behaviour was introduced (along with several other changes in Qt's multithreading support) with version 4.4.

Also, it might be worth expanding further on the PyQt-specific issue. In PyQt, a signal can be connected to a Qt slot, another signal, or any python callable (including lambda functions). For the latter case, a proxy object is created internally that wraps the python callable and provides the slot that is required by the Qt signal/slot mechanism.

It is this proxy object that is the cause of the problem. Once the proxy is created, PyQt will simply do this:

    if (rx_qobj)
        proxy->moveToThread(rx_qobj->thread());

which is fine if the connection is made after the receiving object (i.e. rx_qobj) has been moved to its thread; but if it's made before, the proxy will stay in the main thread.

Using the @pyqtSlot decorator avoids this issue altogether, because it creates a Qt slot more directly and does not use a proxy object at all.

Finally, it should also be noted that this issue does not currently affect PySide.