Why is there no Qt connection mode to automatically select between Qt::DirectConnection and Qt::BlockingQueuedConnection?
Asked Answered
D

1

6

Here is how Qt signal/slot connection works:

  • Direct Connection The slot is invoked immediately, when the signal is emitted. The slot is executed in the emitter's thread, which is not necessarily the receiver's thread.
  • Queued Connection The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread.
  • Blocking Queued Connection The slot is invoked as for the Queued Connection, except the current thread blocks until the slot returns. Note: Using this type to connect objects in the same thread will cause deadlock.

And there's an extra one which is actually the default: Auto Connection If the signal is emitted in the thread which the receiving object has affinity then the behavior is the same as the Direct Connection. Otherwise, the behavior is the same as the Queued Connection."

The default works pretty well as expected in most cases:

  • If within a worker thread, queued execution through a queued connection
  • If within the object thread, execution is made right away

However, when a signal is emited from a thread, you have two options to handle it: "Queued" or "Blocking Queued" Connection.

Why is there no mode that would behave like that:

  • If within a worker thread, blocking queued execution
  • If within the object thread, execution is made right away

Because, as the documentation mentions, using a Blocking Queued Connection in the same thread will cause deadlock....so it's a real pain to handle, I often had to create and manage two signals and connections in my code to handle that:

class A
{
    Q_OBJECT
public:
    A()
    {
        connect( this, SIGNAL(changedFromThread()), this, SLOT(update()), Qt::BlockingQueuedConnection );
        connect( this, SIGNAL(changedNotFromThread()), this, SLOT(update()) );
    }

    void notifySomethingChanged()
    {
        if ( QObject().thread() != thread() )
            emit changedFromThread(); // would dead-lock if in same thread
        else
            emit changedNotFromThread();
    } 

public slots:
    void update()
    {
        // Do some changes to A that cannot be done from a worker thread
    }

signals:
    void changedFromThread();
    void changedNotFromThread();
};

If such a mode (let's call it Qt::AutoBlockingConnection was available), I could have written:

class A
{
    Q_OBJECT
public:
    A()
    {
        connect( this, SIGNAL(changedFromThread()), this, SLOT(update()), Qt::AutoBlockingConnection );        
    }

    void notifySomethingChanged()
    {
        emit changedFromThread(); // would dead-lock if in same thread
    } 

public slots:
    void update()
    {
        // Do some changes to A that cannot be done from a worker thread
    }

signals:
    void changedFromThread();
};

Is there any good reason why a thread-friendly connection was only provided to swap between Qt::DirectConnection and Qt::QueuedConnection but none to swap between Qt::DirectConnection and Qt::BlockingQueuedConnection?

Dickinson answered 18/9, 2017 at 13:42 Comment(0)
D
0

I would guess that the Qt dev team do not want you to specify the connection type at all. I can understand this design, in a sense that intentional blocking should almost always be avoided, even more so if you are blocking the UI Thread.

If you wish to synchronize 2 QObject living in different threads, use signals and slots in both of them, and connect them accordingly.

However, if you are in a Master-Slave scenario (typically QMainWindow owning a QObject with QObject::moveToThread(new QThread)), you can :

  • Listen to signals from the QObject into your QMainWindow
  • Call QObject slots using QMetaObject::invokeMethod into QMainWindow, so the method calls are processed asynchronously by the QObject thread.
Durian answered 25/9, 2019 at 21:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.