[Interest] QML engine, C++ model in secondary thread, signal lag, and infinite change loop

Mitch Curtis mitch.curtis at qt.io
Tue Mar 17 12:03:56 CET 2020


Hi.

> -----Original Message-----
> From: Interest <interest-bounces at qt-project.org> On Behalf Of Federico
> Ferri
> Sent: Tuesday, 17 March 2020 11:30 AM
> To: interestqt-project.org <interest at qt-project.org>
> Subject: [Interest] QML engine, C++ model in secondary thread, signal lag,
> and infinite change loop
> 
> Hi,
> 
> consider this scenario:
> 
> I have a Qt Quick application, and a C++ model which I need to have in a
> separate thread. Since it is not possible* to connect a QObject from another
> thread with the QML engine, I have also a proxy QObject living in the main
> thread, forwarding the signals and slots via other signal/slot connections. This
> can be sumarized in this diagram:
> 
> QML Engine <—> Proxy QObject <—> QObject model [worker QThread]
> 
> 
> Obviously the UI should react to changes in the model, and the model should
> be updated by UI.
> 
> For simplicity, assume the model is a real number, and the UI is a slider to
> change this value.
> 
> From what I can see, the mechanism for avoiding an infinite change loop
> adopted by QtQuick is to generate a valueChanged event (signal) if the value
> being set is different from the old value. So I implement this mechanism also
> on the C++ model side.
> 
> So, for instance, if the slider’s value is 42, and i (programmatically) set it to 42,
> no change event happens; if I set it to 99, a valueChange will be emitted,
> which will also reach the model, set the model state to 99 as well; the latter
> will cause another valueChange to be emitted from C++ back to QML, but it
> will stop there, as the value is already 99.
> Similar thing would happen if directly changing the value on the model.
> 
> So far so good… well, that’s what I naïvely thought before hitting this
> problem:
> 
> Suppose we start again from 42. Dragging the slider will generate
> valueChanged(43), valueChanged(44), etc.. in a burst. Due to the queued
> connection across the QThread boundary, these changes will lag a bit. As
> soon as the model sees the valueChanged(43), will emit a similar signal back
> to the UI. But when that valueChanged(43) signal will reach the UI, due to
> the lag, the UI will be already in the 44 state, i.e. at the point where the
> dragging had stopped. So it will generate another valueChanged(43) towards
> the model, while a similar thing is happening also for the next signal in the
> queue (44), thus generating an infinite sequence of change signals.

I don't know if this is actually what's causing your problem, but you should use the signals that are emitted upon user interaction, not the change signals for properties, as that will cause these kind of headaches with binding loops and stuff. For Slider, that is moved():

https://doc.qt.io/qt-5/qml-qtquick-controls2-slider.html#moved-signal

> Where is the flaw in my design?
> How can this problem be solved in a way that every Qt engineer could say
> “yes, that’s how it should be done”? :-)

I don't have an answer to this, and I don't know if this is helpful, but here's how I did some stuff in a separate thread and then displayed the results in a model:

https://github.com/mitchcurtis/slate/blob/master/lib/autoswatchmodel.h
 
> Note: I would have preferred a lot if the proxy QObject was not needed, i.e.
> if the QML engine could make queued connections to a QObject living in
> another QThread. But that is not the cause of the problem, as with a queued
> connection (between QML engine and the C++ model in the worker thread),
> there still would be lag in signal/slot transport.
> 
> [*]: see "QQmlEngine: Illegal attempt to connect to ... that is in a different
> thread than the QML engine” and suggested solutions to it.
> 
> 
> Best regards,
> Federico Ferri


More information about the Interest mailing list