关于 qt:为什么要避免 QEventLoops 的嵌套?


Why should nesting of QEventLoops be avoided?

在他的 Qt 事件循环、网络和 I/O API 演讲中,Thiago Macieira 提到应该避免嵌套 QEventLoop\\'s:

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.

任何人都可以扩展他所指的内容吗?我维护了很多使用模式对话框的代码,当调用 exec() 时,它们在内部嵌套了一个新的事件循环,所以我很想知道这可能会导致什么样的问题。


  • 嵌套的事件循环会消耗 1-2kb 的堆栈。在典型的 32kb L1 高速缓存 CPU 上,它占用了 5% 的 L1 数据高速缓存。

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

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

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

    对于现代 C 来说,使用 C 20 协程是值得的;周围有一些基于 Qt 的实验,易于构建。

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

    在没有协程的情况下干净地编写异步代码通常是通过状态机完成的,请参阅this answer以获取示例,以及与 QStateMachine 不同的实现的 QP 框架。

  • 个人轶事:我等不及 C 协程可以投入生产了,我现在用 golang 编写异步通信代码,并将其静态链接到 Qt 应用程序中。效果很好,垃圾收集器不引人注目,而且代码比带有协程的 C 更容易读写。我有很多使用 C 协程 TS 编写的代码,但将其全部移至 golang,我不后悔。


    嵌套的事件循环会导致顺序倒置。 (至少在 qt4 上)

    假设您发生了以下一系列事情

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

    所以你看到处理顺序是:1,4,2,3。

    我根据经验说话,这通常会导致我的代码崩溃。