Connecting signal to a slot has no effect

Hi,
I am struggling with the connection of a signal from a threaded data acquisition to a handler method. The signal is emitted just fine, but the slot seems never triggered, no matter if it is within the same object or an external slot.
What am I doing wrong here?

import time
import numpy as np
from PySide6 import QtCore


class DataAcquisition(QtCore.QObject):
    data_signal = QtCore.Signal(dict)

    def __init__(self):
        super().__init__()
        self.running = False
        self.channels = 100
        self.verbose = True

        ## Connect the data signal to a handler method
        ## TODO: This does not work?!
        self.data_signal.connect(self.handle_data)

        ## Create a thread manager to run the data acquisition loop
        self.thread_manager = QtCore.QThreadPool()

    def get_measurement(self):
        """Simulate data acquisition with a dummy counter."""
        return np.random.randint(0, 100, size=self.channels)

    def handle_data(self, data):
        print(f"Received data: {data}")

    def process_data(self, data):
        """Simulate data processing."""
        return data

    def acquisition_loop(self):
        """Acquire data in a loop and send processed data to GUI."""
        self.running = True
        while self.running:
            try:
                ## Acquiring raw data
                raw_data = self.get_measurement()

                ## Prepare data for processing
                processed_data = self.process_data(raw_data)

                ## Emit the processed data as a signal
                ## REMARK: The data must be wrapped as a dictionary with a str key for PySide!
                processed_data = {'processed_data' : processed_data}
                if self.verbose:
                    print(f"Emitting data from DAQ thread {type(processed_data)}: {processed_data.keys()}...")
                self.data_signal.emit(processed_data)

                time.sleep(0.5)

            except Exception as e:
                print(f"Error in data acquisition: {e}")
                self.running = False

    def start(self):
        """Start the acquisition loop."""
        if self.verbose:
            print("Starting data acquisition...")
        ## Run the data processing and display in a separate thread
        self.running = True
        self.thread_manager.start(self.acquisition_loop)

    def stop(self):
        """Stop the acquisition loop."""
        if self.verbose:
            print("Stopping data acquisition...")
        self.running = False
        while self.thread_manager.activeThreadCount() > 0:
            time.sleep(0.1)


if __name__ == "__main__":
    ## Threaded data acquisition
    daq = DataAcquisition()

    ## Connect the data signal to a handler method
    ## TODO: This does not work?!
    class ReceiverClass():
        def handle_data(self, data):
            print(f"ReceiverClass received data: {type(data)}")
    receiver = ReceiverClass()
    daq.data_signal.connect(receiver.handle_data)

    ## Connect the data signal to a handler method
    ## TODO: This does not work?!
    def handle_data(data):
        print(f"Received data: {type(data)}")
    daq.data_signal.connect(handle_data)

    ## Start data acquisition
    daq.start()
    ## Run for a few seconds
    time.sleep(3)  # run for a few seconds
    ## Stop data acquisition
    daq.stop()

Hi @damada
I’m not yet got thru your code…
Quick look, I suspect instance of data_signal is not there same as you can expect, but not sure…
For now, here is simple working example:

from PySide6.QtCore import Qt, Signal, QRunnable, QThreadPool
from PySide6.QtWidgets import QApplication, QLabel, QVBoxLayout, QPushButton, QWidget
import time


class WorkerTask(QRunnable):
    def __init__(self, signal):
        super().__init__()
        self.signal = signal

    def run(self):
        for i in range(5):
            time.sleep(1)
            data = {"count": i, "message": f"Emitted data #{i}"}
            self.signal.emit(data)


class MainWindow(QWidget):
    data_signal = Signal(dict)

    def __init__(self):
        super().__init__()

        self.setWindowTitle("QThreadPool Example")
        self.resize(400, 200)

        self.label = QLabel("Press the button to start")
        self.label.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
        self.label.setWordWrap(True)

        self.button = QPushButton("Start Worker")
        self.button.clicked.connect(self.start_worker)

        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.button)
        self.setLayout(layout)

        self.thread_pool = QThreadPool()
        self.data_signal.connect(self.handle_data)

    def start_worker(self):
        self.button.setEnabled(False)
        worker = WorkerTask(self.data_signal)
        self.thread_pool.start(worker)

    def handle_data(self, data):
        self.label.setText(f"Received: {data['message']} (count: {data['count']})")

        if data["count"] == 4:
            self.button.setEnabled(True)


if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()

I think the problem is that:
The QThreadPool (and its method start()) requires you to wrap your task (function/method) in a class that inherits from QRunnable. It won’t just take a regular function or method directly.

This can be a reason. In my example I pass to QThreadPool instance of WorkerTask and then it working.

1 Like

Dear Michal,
thanks for getting into this. I am still experimenting to find out why this connection is not working. I am successfully connecting this signal in another module in a QMainWindow and in QWidgets without problem. So the parent class does not seem to be a QRunnable, necessarily.
I’ll get back as soon as I know more (or with a corresponding minimal example).

1 Like