当阻塞任务与 GUI 相关时,如何保持 PyQt GUI 响应?

Posted

技术标签:

【中文标题】当阻塞任务与 GUI 相关时,如何保持 PyQt GUI 响应?【英文标题】:How to keep PyQt GUI responsive when blocking tasks are GUI related? 【发布时间】:2015-11-23 15:48:14 【问题描述】:

我正在编写一个应用程序,它将一堆 matplotlib 图形嵌入到 PyQt GUI 中。这些数字的更新可能需要几秒钟,所以我想引入一个等待指示器,以便在绘制绘图时显示。我已将所有数据处理代码移到它自己的线程中,但似乎实际的绘图调用通常占处理时间的大部分。

我编写了一个等待指示器,它使用QTimer 实例在小部件上触发paintEvent。当所有密集处理都可以推入另一个线程时,这工作得很好。问题是,由于Qt 的设计方式,这些构造matplotlib 绘图的调用无法移出主线程,因此阻止了等待指示器的更新,使其有点无用。

我在每个图更新后引入了一些对QCoreApplication.processEvents()的调用,这稍微提高了性能。我还玩弄了猴子修补matplotlib.axes.Axes 的一堆方法以包括对QCoreApplication.processEvents() 的调用的想法,但我可以看到它变得混乱。这是我能做的最好的吗?有什么办法可以定时中断主线程,强制其处理新事件?

【问题讨论】:

我不知道解决这个问题的直接方法。我唯一能想到的就是减少你的绘图时间。似乎您尝试绘制的数据多于您的屏幕分辨率可以查看的数据。您可以在线程中对数据进行下采样,然后从主线程中绘图。可以在缩放时触发下采样,以便您始终将数据量与当前显示相匹配。我以前用 pyqtgraph 做过这个(我认为它比 matplotlib 稍快) 我怀疑您可能对某些数字是正确的。我会试试你的建议,看看能不能有所收获。但是,我认为这只是问题的一部分。数字的数量似乎比数据的大小更重要。 是的,matplotlib 不是很精简,尤其是如果您使用 imshow 之类的东西。我不确定你有多少个数字,但我已经用 pyqtgraph 中的下采样技巧完成了大约 20 个(所有 x 轴都链接在一起)。但我确信它高度依赖数据! 【参考方案1】:

在线程中对QPixmap 进行实际绘图也应该有很大帮助。使用 QPainters drawPixmap() 方法绘制像素图非常快。只有在真正需要时(例如在缩放之后),您才需要重新创建像素图。与此同时,您只需要重用已经绘制的像素图。使用drawPixmap() 的实际paintEvents 几乎没有成本,而且您的GUI 将完全响应。

使用processEvent() 破坏代码不仅丑陋,而且会导致非常讨厌且难以调试的故障。例如。这可能会导致过早删除仍在使用但使用deleteLater() 计划删除的对象。

这个答案也可能有用:Python - matplotlib - PyQT: plot to QPixmap

我还没有使用 matplotlib。但是如果它直接使用 QWidgets 并且没有就不能使用它不会像你上面提到的那么容易。但是您可以在另一个由您的 GUI 启动的过程中进行绘图,该过程使用上面链接中的 matplotlib 并将像素图存储到磁盘,并且只要准备好新的像素图,您的 gui 就会加载。 QFileSystemWatcher 可能有助于避免轮询。

【讨论】:

以上是关于当阻塞任务与 GUI 相关时,如何保持 PyQt GUI 响应?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PyQt5 中保持行和列大小与网格布局相同?

当我使用线程时,PyQt5 中没有更新 GUI

完成后如何自动退出PyQT QThread?

将 Pyqt GUI 主应用程序作为单独的非阻塞进程运行

PyQt5 中的前后端分离与 QML

如何使用python3.5.2+pyqt5编写无阻塞多线程GUI