Why should nesting of QEventLoops be avoided?
Asked Answered
M

2

12

In his Qt event loop, networking and I/O API talk, Thiago Macieira mentions that nesting of QEventLoop's should be avoided:

QEventLoop is for nesting event Loops... Avoid it if you can because it creates a number of problems: things might reenter, new activations of sockets or timers that you were not expecting.

Can anybody expand on what he is referring to? I maintain a lot of code that uses modal dialogs which internally nest a new event loop when exec() is called so I'm very interested in knowing what kind of problems this may lead to.

Mailbag answered 22/2, 2016 at 18:33 Comment(0)
S
13
  1. A nested event loop costs you 1-2kb of stack. It takes up 5% of the L1 data cache on typical 32kb L1 cache CPUs, give-or-take.

  2. It has the capacity to reenter any code already on the call stack. There are no guarantees that any of that code was designed to be reentrant. I'm talking about your code, not Qt's code. It can reenter code that has started this event loop, and unless you explicitly control this recursion, there are no guarantees that you won't eventually run out of stack space.

  3. In current Qt, there are two places where, due to a long standing API bugs or platform inadequacies, you have to use nested exec: QDrag and platform file dialogs (on some platforms). You simply don't need to use it anywhere else. You do not need a nested event loop for non-platform modal dialogs.

  4. Reentering the event loop is usually caused by writing pseudo-synchronous code where one laments the supposed lack of yield() (co_yield and co_await has landed in C++ now!), hides one's head in the sand and uses exec() instead. Such code typically ends up being barely palatable spaghetti and is unnecessary.

    For modern C++, using the C++20 coroutines is worthwhile; there are some Qt-based experiments around, easy to build on.

    There are Qt-native implementations of stackful coroutines: Skycoder42/QtCoroutings - a recent project, and the older ckamm/qt-coroutine. I'm not sure how fresh the latter code is. It looks that it all worked at some point.

    Writing asynchronous code cleanly without coroutines is usually accomplished through state machines, see this answer for an example, and QP framework for an implementation different from QStateMachine.

Personal anecdote: I couldn't wait for C++ coroutines to become production-ready, and I now write asynchronous communication code in golang, and statically link that into a Qt application. Works great, the garbage collector is unnoticeable, and the code is way easier to read and write than C++ with coroutines. I had a lot of code written using C++ coroutines TS, but moved it all to golang and I don't regret it.

Shake answered 22/2, 2016 at 19:21 Comment(3)
Thanks for your answer. Do you have any idea on "new activations of sockets or timers that you were not expecting"?Mailbag
@Mailbag That's what is meant by reentering your code. The code where you call exec can be, generally speaking, called again. The source of such events could be the UI, but it could be timers, sockets, native platform events, etc.Attitudinarian
State machine is like a missing paradigm to complement object-oriented, functional and declarative programming.Eritrea
T
0

A nested event loop will lead to ordering inversion. (at least on qt4)

Lets say you have the following sequence of things happening

enqueued in outer loop: 1,2,3
processing 1 => spawn inner loop
enqueue 4 in inner loop
processing 4
exit inner loop
processing 2

So you see the processing order was: 1,4,2,3.

I speak from experience and this usually resulted in a crash in my code.

Threlkeld answered 17/1, 2018 at 8:33 Comment(3)
Interesting. I assume there was an ordering constraint in your code that made it crash? Can you go more into detail about what kind of events these were? I don't see a priori how enqueueing event 4 in the inner loop would make it be processed first or crash your code.Mailbag
Of course the code relied on some sequence of messages. A pointer would be initialized on first event and the object invoked on the second event and crash with seg-fault. Its a valid thing to rely on ordering if things are not idempotent.Threlkeld
@Mailbag This thing was recently an issue in my team as someone wanted to make an HTTP request and wanted to wait for result with inner loop. Eventually he settled to just block the eventloop, that is if its not a UI thing otherwise things can get messy because introducing an async step would be quite viral for the APIs but if you took the qt, then be ready to pay the price :)Threlkeld

© 2022 - 2024 — McMap. All rights reserved.