[Interest] synchronous use of Qt state machine

Volker Hilsheimer volker.hilsheimer at qt.io
Wed Feb 8 17:20:06 CET 2023


On 2 Feb 2023, at 20:27, Stefan Seefeld <stefan at seefeld.name> wrote:

Hello,

I haven't got any response to my question, but as the answer may really help us simplify our code I'm sending it again.
Thanks for any help !

On Tue, Nov 22, 2022 at 9:02 AM Stefan Seefeld <stefan at seefeld.name<mailto:stefan at seefeld.name>> wrote:
Hello,
we are using Qt State Machines in a number of places in our applications to manage object states for certain types of objects. Occasionally I would like to use and manipulate such objects in a non-event-driven context, i.e. without a running event loop.
Short of a StateMachine function that allows me to wait for outstanding transitions to be completed, I wrote a little helper function that instantiates a `QEventLoop`, then calls `processEvents(QEventLoop::ExcludeUserInputEvents)` to drain the event queue and thus make sure that all in-flight transitions have been completed.
While this appears to be working fine, I'm not sure this is the best way (or at the very least, a particularly elegant way) to achieve what I want.

Is the above a "correct" way to get what I want ? Is there a better way ?

--

On 4 Feb 2023, at 20:11, Stefan Seefeld <stefan at seefeld.name> wrote:

Hi Roland,

thanks for your feedback !
I partially agree with your analysis. In particular, I'm not entirely sure why the entire application logic needs to gravitate around a single event loop. For user-input events this of course makes sense, as they are naturally ordered and so conceptually we don't need concurrency to model the related control-flow. (We do need asynchrony, though !)

My main puzzlement stems from the fact that the event loop is used in places where I don't expect it, such as in the handling of state machines. Of course, state changes may well be triggered by user input (or some other events), but they don't have to, so it seems questionable why the entire state machine architecture has to be founded on the event loop as well. It could be independent, with some mix-in classes that allow state machines to interact with the event loop, without forcing such a dependency on everyone.

But to get back to the smaller scope of my original question, users don't even have to access the main event loop via the `QApplication::processEvents()` function; they can just create their own local `QEventLoop` instance and use that. (Of course, under the hood that has to interact with the main event loop, which is why this only works in the presence of a global `QApplication` instance, but that interaction is luckily already hidden from the user.)

My question is: Is it OK to use my own local `QEventLoop` to drain events to render state-change requests synchronous. And furthermore, if the answer is "yes", is there a reason why such a facility isn't already offered by the Qt API itself ?

I'd really like to hear what Qt developers have to say about this, and, as you suggest, about the broader question of how to scale Qt applications to modern many-core platforms. How would users write modern C++-2x applications while using `co_await` ? There already are adapters for the Python equivalent, using `QEventLoop` instances to manage events and dispatch control flow to multiple threads (e.g. https://github.com/CabbageDevelopment/qasync). It seems to me what I'm asking for is something similar, if not much simpler).

Thanks,

Hi Stefan,


Where do you see the advantages of synchronously waiting for a state to be reached, when instead you can connect to the respective state’s entered() signal? The former might make your code look more linear of course, with less complexities due to object lifetimes etc.

When the QtState Machine framework was added it was done so in the context of a UI framework, so a core design choice was to drive the state machine asynchronously so that the UI doesn’t freeze. And since then, we had no compelling reason to modify that architecture. It seems to work well enough in the context of a UI framework, where state transitions are triggered by events. Or maybe people have silently migrated to alternative state machine frameworks.

QStateMachine could live in it’s own thread which can run its own event loop. States emit signals, so objects interesting in state enter/exit notifications can connect to those signals even if they live in threads other than the state machine. And a QSignalTransition could operate on a sender living in a different thread as well (QEventTransition can’t support that though). So asynchronicity could be achieved that way as well, but it doesn’t solve your problem of stopping your code until a state has been reached. The design of Qt State Machine is that you put that code into a callable connected to the respective signals.

That is a general question. We probably don’t want more waitFor… style APIs in Qt. If we would add such APIs, they might very well be implemented by a local QEventLoop that runs until the signal you are waiting for gets emitted. So your solution is reasonable. Why is it not in Qt? Because nobody asked for it, needed it, or contributed a solution so far. And perhaps it’s ok to leave that solution to the client, as not every waitFor… use case might want to e.g. ExcludeUserInputEvents.

Co-routines might at some point become the standard tool also for Qt application developers to solve that kind of problem. What that could look like continues to be a subject of research. Ville has commented on that in the past [1] and has recently played with Qt and sender/receiver (i.e. P2300), which got even Eric Niebler excited [2].

Volker


[1] https://lists.qt-project.org/pipermail/development/2021-March/041110.html
[2] https://twitter.com/ericniebler/status/1616180736914264066





-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.qt-project.org/pipermail/interest/attachments/20230208/a299ff60/attachment.htm>


More information about the Interest mailing list