在Qt中多线程绘制

5
我正在使用Qt编写一个程序,其中有10个工作线程计算物体在空间中的轨迹,并且必须绘制物体的路径。我有一个“Body”类,它派生自QGraphicsEllipseItem,并且其中包含一个QPainterPath。 "Simulation"类接受世界上障碍物的列表和要模拟的物体,并运行直到物体与某些东西碰撞。模拟在单独的线程中运行(通过moveToThread完成,而不是通过子类化QThread)。当物体发生碰撞时,模拟会发出信号表示已完成。当所有线程都完成时,我想绘制路径(通过调用“Body”中的方法,在其draw方法中启用路径绘制)。

不幸的是,我得到了ASSERT错误:

ASSERT: "!unindexedItems.contains(item)" in file graphicsview\qgraphicsscenebsptreeindex.cpp, line 364

它们似乎是随机发生的。我已经尝试了不同的连接类型,但没有结果。
我正在循环中启动线程。
我正在使用Qt 5.0。

2个回答

11

一般而言,使用Qt时,在GUI线程之外不能执行任何GUI操作(即执行QApplication::exec()的线程,通常是main()线程)。

因此,如果您有多个线程操纵QGraphicsItems(特别是当前作为QGraphicsScene一部分的QGraphicsItems),那么这很可能是断言失败的原因。也就是说,在Qt GUI线程执行窗口刷新时,它会从各种QGraphicsItem对象中读取数据作为其计算的一部分,并且期望QGraphicsItems在刷新操作期间保持不变。如果在刷新例程执行过程中更改了QGraphicsItem(由另一个线程),则主线程进行的计算可能会变得错误/损坏,并且偶尔会引起断言失败(和/或其他不希望的行为)。

如果您确实需要使用多个线程,那么您可能需要让线程在自己的私有数据结构上进行所有计算,Qt GUI线程无法访问该数据结构。然后,当线程计算出结果时,它们应将结果发送回Qt GUI线程(通过queued connection或QApplication::postEvent())。然后,GUI线程可以查看结果并使用它们更新QGraphicsItems等;这将是“安全”的,因为此更新不能在窗口更新的中间发生。

如果这听起来太麻烦,那么您可以考虑只在GUI线程中进行所有操作;以这种方式让一切都能可靠地运行会更容易和简单。


理想情况下,这些根本不应该是Qt对象,尽管如果你非常小心的话可能可以使用某些Qt类?有大量的类可以在GUI线程之外使用...有数字会很有趣,但我认为只有一小部分Qt不能在GUI线程之外使用。 - Luca Carlon
@LucaCarlon 我的观点是,如果您使用Qt类,您需要验证它们是否安全可靠,而如果您使用非Qt类,您几乎可以保证它不会与Qt GUI线程产生任何非明显的交互。 - Jeremy Friesner
我认为说:“理想情况下,这些根本不应该是Qt对象”远远不止是误导。Qt类旨在从任何线程中使用。只有一小部分(可能大多数都是从QWidget、QPixmap和其他一些类继承的)不是这样。但是所有其他类都可以在任何线程中使用。您甚至可以在其他线程上使用QPainter绘制QImages和QPaintDevice等...更不用说其他模块,如QtSql、QtXml等了... - Luca Carlon
@LucaCarlon:在尝试使用QPainter在QImage上绘制文本之前,祝你好运:)...是的,Jeremy是对的,不鼓励在不需要的地方使用Qt对象。除了GUI本身之外,还有更好的替代品来完成任何任务。 - Yakov Galka
QPainter和QImage都在gui模块中,而且你可以在主线程之外美观地处理QImage(来自文档):“当在QImage上使用QPainter时,绘画可以在另一个线程中执行,而不是当前的GUI线程”。你在特定平台上使用QImage上的字体遇到问题并不意味着Qt的数百个类不能在主线程之外工作。Qt中有大量的代码位于该模块之外:Qt Core、Qt Multimedia、Qt Network、Qt Sql、Qt Bluetooth、Qt WebSocket等等...而且多线程是大多数应用程序的关键部分。 - Luca Carlon
显示剩余2条评论

2

如 Jeremy 所提到的,Qt 渲染必须在主线程上完成。

尽管您可以将所有内容移动到主线程上,但很可能会选择为了效率而创建单独的线程,特别是因为碰撞检测可能会占用处理器。最好的方法是将对象的建模和物理分离出来,就像在模型/视图/控制器模式中一样。

创建不从任何 QGraphicsItem/Object 派生的身体实例表示。这些实例可以在不同的线程上进行计算,并向在主线程上运行的图形对象发出信号,该信号更新每个身体实例的图形表示,从而允许轨迹实时呈现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接