Sending Signals to Non-Qt-Thread - How?



  • Hi Qties,

    I have a simple goal: For thread-safe IPC I want to be able to send signals from a Qt-Gui-thread to a non-Qt-thread (read: does not use the Qt event loop). The solution is a lot more complicated than I thought, and I start to fear that it's not possible at all.

    I am experimenting with an alternative Qt/QML based GUI for our existing application (I will call it "backend" in this case). Therefore I wrote a simple QApplication that starts the backend in another QThread and an interface class that lives in both worlds/threads. The other direction, sending signals from the backend to the Qt thread was easy and works like a charm. But sending signals from Qt to the backend is harder than I thought.

    I don't want to regularly poll the event queue with hasPendingEvents() since it either induces delay or a performance burden. What I have so far is a local pipe to wake up the poll of the backend event loop in order to call processEvents() of the event dispatcher of the backend QThread. So the problem is now to trigger the wake-up whenever a signal is sent to the backend thread or, more precisely, whenever a postEvent() is done for a QObject belonging to the backend thread. Unfortunately I did not manage to achieve this yet. All options that come to my mind seem to have a dead-end. Here are my options so far:

    Replacing the eventloop of the backend with a Qt eventloop is not really an option, since we have our own pollset and timer mechanism, that is used all over the code. Since it's quite low level it's not easy to write a wrapper for it, and still the code has to remain compilable as a non-Qt-version.

    QAbstractEventDispatcher seems to be perfect to write a simple event dispatcher only for Qt events, which just triggers the pipe in the wakeUp() routine. The question is: While it's easy to install your own event dispatcher in the GUI thread, how do you install it in another QThread? I ended up reading the Qt sources and found the line: "// ### TODO: allow the user to create a custom event dispatcher" in corelib/thread/qthread_unix.cpp. Bummer!

    I found qt_register_signal_spy_callbacks() in corelib/kernel/qcoreapplication.cpp, with which I might capture the cases when a signal is emitted from the GUI to the backend. But this really doesn't look like an official way to solve problems, and I would have a burden on all signals that are emitted, not only that connected by Qt::QueuedConnection.

    I checked if from emitting the signal until the insertion into the event queue there are any other signals emitted that I could connect to, but I didn't find any. Also I don't see a possibility to install such signals, like in the generated meta object code, or by defining a notify callback for the event queue, like you can do on properties.

    I could just manually or by macro call the wake-up whenever I emit a signal of which I know it is connected to the backend thread. This is ugly, and I would only do it as a last resort.

    Of course the QAbstractEventDispatcher is the cleanest solution, but I want to stick with a standard Qt without custom patches and this is probably not on a short-term agenda of the Qt developers. So I ask you guys, do you have any other idea or suggestion how to solve this problem? Any hint is highly appreciated!

    Thanks in advance and best regards,

    Sven



  • Hi ansiwen,

    if you want to use signal/slot connection between threads, you must have an event queue (q Qt event queue) or do all the stuff on your own. AFAIK there is no other solution for that.



  • I'd go with 6: patch Qt and install your own QAbstractEventDispatcher for your thread :)



  • [quote author="peppe" date="1311801325"]I'd go with 6: patch Qt and install your own QAbstractEventDispatcher for your thread :)[/quote]

    That's basically 2. As I wrote, I also think, this is the cleanest solution, but I don't want to use a patched version for Qt. So, if I'd do that, I would do it in a way that it has good chances to get merged into the official Qt branch. There are several ways, how I could implement this. Therefore I need to know first, what's the best way in the eyes of the Qt devs.

    So, to whom do I have to talk to? Preferably it's the person that also decides, if the patch is accepted or not.



  • #qt @ freenode or the "official mailing list":http://lists.qt.nokia.com.



  • Just in case somebody ends up here with a similar problem: I implemented a new API to install event dispatcher in threads and submitted it half a year ago. It will be part of Qt 5. Here's the changeset (which also can be applied to Qt 4.8): https://codereview.qt-project.org/#change,8628



  • Interesting. I think your patch doesn't quite work as advertised though. It states that you can set a different event dispatcher as long as the thread has not started yet, but in fact you can set a dispatcher as long as no dispatcher has been set yet. So, this hypothetical example would not work:
    @
    QThread thread = new QThread(this);
    QAbstractEventDispatcher* d1 = new MyCustomEventDispatcher();
    thread->setEventDispatcher(d1); //no problems here...
    //...
    QAbstractEventDispatcher* d2 = new MyEvenMoreCustomEventDispatcher();
    thread->setEventDispatcher(d2); //fails! This is not expected IMHO

    thread->start();
    QAbstractEventDispatcher* d3 = new MyEvenMoreCustomEventDispatcher();
    thread->setEventDispatcher(d3); //fails too, as expected
    @

    Perhaps I misunderstood, but can you explain why installing d2 needs to fail? I'd say that as long as the thread is not started yet, there is no reason to disallow changing your mind on what dispatcher should be installed.



  • [quote author="Andre" date="1335515650"]Interesting. I think your patch doesn't quite work as advertised though. It states that you can set a different event dispatcher as long as the thread has not started yet, but in fact you can set a dispatcher as long as no dispatcher has been set yet.
    [/quote]

    The commit message is a bit misleading, but the documentation says "This is only possible as long as there is no event dispatcher installed for the thread yet." So this is a "set-once" operation.

    [quote]
    Perhaps I misunderstood, but can you explain why installing d2 needs to fail? I'd say that as long as the thread is not started yet, there is no reason to disallow changing your mind on what dispatcher should be installed.
    [/quote]

    The reason is KISS. It is very simple just to check, if there is an event dispatcher present. To re-set it, you would have to check, if the thread is running already and cleanly remove the old one. Also, I adapted the previous behavior: You couldn't instantiate two event dispatcher in Qt4, the second instantiation would fail for the same reason.

    This is a rarely used feature, and the need to re-set the event dispatcher is even more rare, if existing at all. So, as long as there is no really good use case, I wouldn't take the effort and risk of implementing re-setting of the event dispatcher.



  • Thanks for your explanation. I don't quite see how checking the runstate would be more difficult than checking if a dispatcher has already been set. Isn't that as simple as this?

    @
    if (isStarted()) {
    ...
    }
    @

    The problem is, IMHO, is that it violates the property-based API that is recommended for Qt classes. I, at least, don't expect setters to act this way, and AFAIK, this behavior doesn't exist elsewhere in Qt.

    On the other hand: I get that setting the property multiple times is highly unlikely, as setting it once is already something you do hardly ever.



  • Well, you have to see that - although I wrote that patch - I'm quite a Qt newbie. So for me it is more trouble, since I have to read more code to make sure I do it right. Also none of my "Qt dev mentors" asked for this. So it is like it is, since it meets my needs, but as you know, you are invited to further improve it. ;-) (Although I wouldn't really see this as a Qt property)


Log in to reply
 

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