Circular SIGNAL SLOT connection. Is it possible?



  • Hello!

    While playing around in Qt I recognized something, that doesn't make sense to me:

    If I have SLOT A emit SIGNAL B and then connect SIGNAL B to call SLOT A, it doesn't take long to get a segmentation fault error.
    I kind of get why that happens with recursive functions, because the every function has to be kept alive until the last one is finished.

    Why is that (apparently) the same with SIGNALs and SLOTs?
    Here is some code snippet:

    // Function definition
    void CircularSignalSlotConnection::doSomething()
    {
        // <5000 works, >6000 throws segmentation fault
        if (counter<5000)
        {
            counter++;
            emit doAgain();
        }
        else qDebug() << counter;
    }
    
    // Connect the function to call itself
        connect(this, SIGNAL(doAgain()), this, SLOT(doSomething()));
    
    // Initiate the "recursion"
        counter = 0;
        doSomething();
    

    Does the function have to be kept alive, until the emitted SIGNAL was "dealt with"?
    And even if it were like that, the signals can't be emitted faster, than the functions compute... so each signal has to be off the event loop before a new one gets emitted... I am confused.

    Can someone explain this to me?
    And can I deliberately "end" the function call to make the code above work for as long as I want?

    This is nothing I need for a specific problem I have... it's just something I want to wrap my head around.

    Kind regards,

    Mr. Floppy.


  • Lifetime Qt Champion

    Hi,

    No need for a signal here. Something along:

    if (counter<5000) {
        counter++;
        QTimer::singleShot(0, this, &doSomething);
    }
    

    would be cleaner



  • @Mr.Floppy

    Check out this 2-part blog post.

    https://woboq.com/blog/how-qt-signals-slots-work.html

    If I remember correctly, if the object that emits the signal and the object with the slot connected to that signal live in the same thread: signal-slot invokations are just regular nested function calls.



  • @t3685
    Thank you!
    The blog-post mentions the following code snippet:

            QObject * const receiver = c->receiver;
            const bool receiverInSameThread = QThread::currentThreadId() == receiver->d_func()->threadData->threadId;
    
            // determine if this connection should be sent immediately or
            // put into the event queue
            if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                || (c->connectionType == Qt::QueuedConnection)) {
                /* Will basically copy the argument and post an event */
                queued_activate(sender, signal_index, c, argv);
                continue;
            } else if (c->connectionType == Qt::BlockingQueuedConnection) {
                /* ... Skipped ... */
                continue;
            }
    

    So you are correct.
    Whenever possible, a signal will result in a simple function call.
    To be sure to post an event, you'll have to manually specify the connection type as Qt::QueuedConnection.
    When I do that in my previous example it works as intended :)

    connect(this, SIGNAL(doAgain()), this, SLOT(doSomething()),Qt::QueuedConnection);
    

    @SGaist
    Thanks to you too!
    Although your code doesn't explain anything, it also works as intended.
    Maybe because QTimer posts events innately?

    Out of curiosity I "benchmarked" all discussed methods.
    [EDIT] The first time all methods counted up the same counter and have used the same QElapsedTimer... that wasn't very smart... I have corrected that and now counting via event takes way longer than iterative counting... which makes way more sense.
    But the main point was to understand what went wrong in the first place, and that's solved.

    Thanks again for the quick help! I have learned something :)

    Kind regards,

    Mr. Floppy.

    [EDIT] Results (one method at a time):

    Iterative counting to 1000000 took me 2 ms.
    Iterative counting to 10000000 took me 25 ms.
    Iterative counting to 100000000 took me 243 ms.
    
    Queued counting to 1 took me 19 ms.
    Queued counting to 10000 took me 232 ms.
    Queued counting to 100000 took me 1947 ms.
    
    Timed counting to 1 took me 18 ms.
    Timed counting to 10000 took me 301 ms.
    Timed counting to 100000 took me 2789 ms.
    

    [EDIT] Code:

    #include "circularsignalslotconnection.h"
    #include "ui_circularsignalslotconnection.h"
    #include <QDebug>
    #include <QTimer>
    
    CircularSignalSlotConnection::CircularSignalSlotConnection(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::CircularSignalSlotConnection)
    {
        ui->setupUi(this);
        stopCountingAt = 100000;
        // Just count like a normal person
        elapsedTimerC.restart();
        counterC = 0;
        while (counterC < stopCountingAt) ++counterC;
        qDebug() << "Iterative counting to" << counterC << "took me" << elapsedTimerC.elapsed() << "ms.";
        // Connect doSomething to itself
        connect(this, SIGNAL(doAgain()), this, SLOT(doSomething()), Qt::QueuedConnection);
        // Count via circular SIGNAL SLOT connection
        elapsedTimerA.restart();
        counterA = 0;
        doSomething();
        // Count via QTimer
        elapsedTimerB.restart();
        counterB = 0;
        doSomethingWithQTimer();
    }
    
    CircularSignalSlotConnection::~CircularSignalSlotConnection()
    {
        delete ui;
    }
    
    void CircularSignalSlotConnection::doSomething()
    {
        if (counterA < stopCountingAt)
        {
            ++counterA;
            emit doAgain();
        }
        else qDebug() << "Queued counting to" << counterA << "took me" << elapsedTimerA.elapsed() << "ms.";
    }
    
    void CircularSignalSlotConnection::doSomethingWithQTimer()
    {
        if (counterB < stopCountingAt) {
            ++counterB;
            QTimer::singleShot(0, this, &doSomethingWithQTimer);
        }
        else qDebug() << "Timed counting to" << counterB << "took me" << elapsedTimerB.elapsed() << "ms.";
    }
    

  • Lifetime Qt Champion

    A QTimer in single shot mode with a 0 timeout means that your slot will be called next time the event loop runs. This means that your application is still "alive". When using a while loop like that, you are blocking the event loop from processing.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.