Emitting Signal from Slot problem in multithreaded environment



  • Hi

    I've encountered a problem trying to get classes to communicate between threads. I couldn't find any information about this particular scenario so not sure if it's expected behaviour or not.

    I have ObjectA which is created on thread A.
    And ObjectB is created on thread B.

    A signal from ObjectA is connected to a slot on ObjectB, as a QueuedConnection.
    And vice verse, a signal from ObejctB is connected to a slot on ObjectA as a QuededConnection.
    I've tried various combinations of connection types (however they need to be either Queued or BlockingQueued), none solve the problem.

    In the slot for ObjectB, I emit the ObjectB's signal (connected back to ObjectA).

    Emitting ObjectA's signal, I expect both slots to be called. Only one does, ObjectB's. The connection from ObjectB to ObjectA succeeds, however the slot is never called.

    Stepping through the Qt code when emitting seems to suggest a problem occurs in qcoreapplication.cpp in QCoreApplication::postEvent at the end of the function, the following lines:

    QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
        if (dispatcher)
            dispatcher->wakeUp();
    

    the return from loadAcquire always returns NULL for the second signal.

    A minimal example I managed to replicate this behaviour in is below.

    #ifndef MAINOBJECT
    #define MAINOBJECT
    
    #include <iostream>
    #include <QObject>
    
    class MainObject : public QObject
    {
        Q_OBJECT
    
    public:
        MainObject(){}
    
    public slots:
        void mainObjectSlot()
        {
            std::cout << "Main slot\nEmitting main signal...\n";
            emit mainObjectSignal();
        }
    
    signals:
        void mainObjectSignal();
    };
    
    #endif // MAINOBJECT
    
    #ifndef WORKERTHREAD
    #define WORKERTHREAD
    
    #include "mainobject.h"
    
    #include <QThread>
    
    class WorkerObject : public QObject
    {
        Q_OBJECT
    
    public:
        void emitSig()
        {
            std::cout << "Emitting worker signal...\n";
            emit workerObjectSignal();
        }
    
    public slots:
        void workerObjectSlot()
        {
            std::cout << "Worker slot\n";
        }
    
    signals:
        void workerObjectSignal();
    };
    
    class WorkerThread : public QThread
    {
        Q_OBJECT
    
    private:
        MainObject *m_mainObj;
    
    public:
        WorkerThread(MainObject *obj) : m_mainObj(obj) {}
    
    public slots:
        void run() Q_DECL_OVERRIDE
        {
            WorkerObject *obj = new WorkerObject;
    
            if(!connect(obj, SIGNAL(workerObjectSignal()),
                    m_mainObj, SLOT(mainObjectSlot()),
                    Qt::QueuedConnection))
            {
                std::cout << "Failed to connect worker signal to main slot.\n";
            }
            if(!connect(m_mainObj, SIGNAL(mainObjectSignal()),
                    obj, SLOT(workerObjectSlot()),
                    Qt::QueuedConnection))
            {
                std::cout << "Failed to connect main signal to worker slot.\n";
            }
    
            obj->emitSig();
        }
    };
    #endif // WORKERTHREAD
    
    #include <QCoreApplication>
    
    #include "workerthread.h"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        WorkerThread thread(new MainObject);
        thread.start();
    
        return a.exec();
    }
    

    I get the following output when I run this.
    0_1472566972085_Qt.PNG

    I am using Qt version 5.4.1 on Windows.

    Any help would be appreciated.


  • Qt Champions 2016

    class WorkerThread : public QThread
    {
        Q_OBJECT
    
        // ...
    protected:
        void run() Q_DECL_OVERRIDE //< run() isn't a slot and should be left protected.
        {
            // ...
            exec() //< You can't have queued connections without an event loop
        }
    };
    


  • Thanks, that definitely solved the problem with the example.

    Is there a way to have queued connections work within another thread when the thread is not within a QThread, ie. when exec() is not available? In my actual code the thread is created using AfxBeginThread().



  • you will need a Qt event loop to process Qt signals with queued connections. given you have to mix MFC and Qt I'd use a signal system that is independent from both while still being thread safe to make them interact. The first thing that comes in mind is, of course, Boost.Signals2 http://www.boost.org/doc/libs/1_61_0/doc/html/signals2.html



  • @Floofy.KH

    IIRC QThread::exec() starts a event loop (QEventLoop). Signals coming from a different thread a placed as events in the event loop of the receiving thread. I do not think it's possible to have Qt event handling without this event loop. Maybe you can use your own QEventLoop object AfxBeginThread thread?



  • @t3685

    Thanks. That seemed to solve the problem. Creating a QEventLoop object inside the thread and calling exec() on it allowed all events to be processed correctly.

    Thanks for all the help.

    edit: @VRonin Using an independent signalling system such as Boost would be a good idea too, which I will consider. Thanks.


  • Qt Champions 2016

    @Floofy.KH
    @t3685 beat me to it, this is what I would have suggested. QThread::exec calls QEventLoop::exec anyway (same with QCoreApplication::exec), so you can substitute it with a call to the local event loop most of the time. I would, however, also suggest you try and migrate the WinAPI portions of your code to Qt (i.e. not creating the threads with AfxBeginThread), which incidentally will also make your code portable.

    Kind regards.



  • @kshegunov @t3685 Won't calling QEventLoop::exec from the MFC thread block it until exec returns?


  • Qt Champions 2016

    @VRonin
    It will, but I assume that's one of the reasons he's starting the thread to begin with.



  • @kshegunov Let me rephrase: aren't we just flipping the pancake here? if you don't call QEventLoop::exec() you won't be able to process Qt signals, but if you call it then MFC will not be able to process its events any more.


  • Qt Champions 2016

    @VRonin said in Emitting Signal from Slot problem in multithreaded environment:

    Let me rephrase: aren't we just flipping the pancake here?

    I honestly don't know, we may be. The thing is I have only partial information on what are the requirements and reasoning behind them, so I can at most guess about the initial intent.

    If you don't call QEventLoop::exec() you won't be able to process Qt signals, but if you call it then MFC will not be able to process its events any more.

    I have forgotten most of the MFC-related API, but what events does it needs processing (in a thread)? I understand that for the application there's integration with the system event loop (both for Qt and for MFC), but I believe there's no system event loop for threads. On a related note, the event loop is needed only for queued connections, so he can use direct connections (with proper locking) without it, thus the boost's signal support sadly doesn't bring anything new here.



  • @kshegunov True, we don't have enough info. but MFC allows to run a new GUI thread: https://msdn.microsoft.com/en-us/library/b807sta6.aspx and that would not work if we call QEventLoop::exec().

    so he can use direct connections (with proper locking)

    I thought Boost.Signal2 was exactly that. mutex-ed direct connection signals


  • Qt Champions 2016

    @VRonin said in Emitting Signal from Slot problem in multithreaded environment:

    and that would not work if we call QEventLoop::exec()

    You won't get any argument from me on that one.

    I thought Boost.Signal2 was exactly that. mutex-ed direct connection signals

    I didn't know it existed, but yes, I suppose that's a possible solution. The only problem I envision with that approach, taking in mind that new piece of information, is that mixing Qt's signal slot system and an external one is (sometimes) a big pain in the ass ...


Log in to reply
 

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