且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

为什么应该避免嵌套 QEventLoops?

更新时间:2023-11-10 12:36:04

  1. 嵌套的事件循环会消耗 1-2kb 的堆栈.在典型的 32kb L1 缓存 CPU 上,它占据了 L1 数据缓存的 5%,无所适从.

  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.

它可以重新输入调用堆栈中已有的任何代码.不能保证任何代码都被设计为可重入的.我说的是你的代码,而不是 Qt 的代码.它可以重新输入已启动此事件循环的代码,除非您明确控制此递归,否则无法保证您最终不会耗尽堆栈空间.

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.

在当前的 Qt 中,由于长期存在的 API 错误或平台不足,您必须在两个地方使用嵌套的 exec:QDrag 和平台文件对话框(在某些平台上).您根本不需要在其他任何地方使用它.您不需要非平台模式对话框的嵌套事件循环.

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.

重新进入事件循环通常是由编写伪同步代码引起的,其中有人感叹缺少 yield()(co_yieldco_await 现在已经在 C++ 中了!),把头埋在沙子里,用 exec() 代替.这样的代码通常最终成为几乎不好吃的意大利面,并且是不必要的.

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.

对于现代 C++,使用 C++20 协程是值得的;有一些基于Qt的实验,易于构建.

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

有堆栈协程的 Qt 原生实现:Skycoder42/QtCoroutings - 最近的一个项目,以及较旧的 ckamm/qt-coroutine.我不确定后面的代码有多新鲜.看起来这一切都在某个时候奏效了.

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.

在没有协程的情况下干净地编写异步代码通常是通过状态机来完成的,例如参见这个答案,以及QP 框架,用于与 QStateMachine 不同的实现.

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.

个人轶事:我迫不及待地希望 C++ 协程可以投入生产,现在我用 golang 编写异步通信代码,并将其静态链接到 Qt 应用程序中.效果很好,垃圾收集器不明显,并且代码比带有协程的 C++ 更容易阅读和编写.我有很多代码是用 C++ 协程 TS 写的,但都搬到了 golang 上,我不后悔.

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.