PyQt5 在不冻结 GUI 的情况下绘制数据的最佳方法 [关闭]

Posted

技术标签:

【中文标题】PyQt5 在不冻结 GUI 的情况下绘制数据的最佳方法 [关闭]【英文标题】:PyQt5 Best way to plot data without freezing GUI [closed] 【发布时间】:2021-08-30 11:38:20 【问题描述】:

我正在尝试实现类似于this 的目标。具体来说,我有一个充满数据的队列,我想实时绘制这些数据。 我目前有一个使用 QTimer(并且没有线程)的工作版本,以便每半秒调用一次函数。此函数将项目从队列中取出(当它非空时)并绘制它们。虽然这肯定有效,但我想知道是否有更好的方法可以使用(涉及线程等)。我主要担心的是我害怕我的方法虽然有效,但会使事情变得比他们需要的更迟钝。我尝试了上面给出的方法,但是(如果我没有搞砸的话,我可能就是这样):

    终止线程有问题(特别是:“QObject::killTimer: 定时器不能从另一个线程停止”) 正如最后一条评论所指出的那样,该进程甚至似乎都没有发生在其他线程中(在其他线程中添加睡眠调用会冻结整个 GUI)。

关键是,我想知道是否有比我目前的方法“更好”的方法来做到这一点(例如上面给出的方法,如果我可以让它在我的情况下正常工作,或者有一个检查线程队列不断地,并且只要它非空,就会发出一个值而不是有一个计时器)。

我没有给出一个最小的可重现示例,因为这不一定是特定代码位的特定问题,更多的是关于正确方法的问题。

编辑:基于this,最好一次只处理队列中的一个事件并将计时器的超时设置为 0 毫秒(而不是每半秒循环一次直到队列不为空) ?

【问题讨论】:

【参考方案1】:

这取决于您希望如何绘制数据。队列中的每个项目都是新图还是只需要显示的最新数据?如果您只需要显示最新数据,那么这是一个简单的过程。

我创建了一个库 qt_thread_updater 来帮助解决这个问题。 qt_thread_updater 使用一个定期运行的计时器来调用主线程中的函数。它是线程安全的。但是,qt_thread_updater 将在稍后的超时时间调用该函数,因此 GUI 更新将延迟几分之一秒。

import multiprocessing as mp
import threading
from qtpy import QtWidgets
from qt_thread_updater import get_updater


app = QtWidgets.QApplication()

fig = Plot()  # Your plotting library
fig.show()

def plot_data(data):
    # Plot the data here
    fig.plot(data)    

def read_queue(que, alive):
    while alive.is_set():
        for _ in range(que.qsize()):
            data = que.get()
            get_updater().call_latest(plot_data, data)
        time.sleep(0.1)  # Some delay to let the QEvent loop process events.

# Processed data queue and Event to close down processes
myque = mp.Queue()
alive = mp.Event()
alive.set()

# Read processed data thread and display
pull_data_th = threading.Thread(target=read_queue, args=(myque, alive))
pull_data_th.start()

# Worker process
worker = mp.Process(target=do_work, args=(myque, alive)
worker.start()

app.exec_()
alive.clear()
worker.join()
pull_data_th.join()

如果您需要显示队列中的每个项目,那么您必须注意性能。如果 QEvent 循环没有时间处理事件并已满,则应用程序将崩溃。如果您的应用程序崩溃,那么您需要降低数据速率或获取更快的绘图库(matplotlib 很慢,pyqtgraph 要快得多)。

除了将call_latest更改为call_in_main之外,您可以使用与以下相同的代码

def read_queue(que, alive):
    while alive.is_set():
        for _ in range(que.qsize()):
            data = que.get()
            get_updater().call_in_main(plot_data, data)

        # Some delay to let the QEvent loop process events.
        if que.empty():
            time.sleep(0.5)
        else:
            time.sleep(0.001)

【讨论】:

我不确定我是否理解 call_latest() 和 call_in_main() 之间的区别,但这可能与您关于我希望如何绘制数据的问题有关。我想基本上增量地绘制一个函数(如 y=x)。也就是说,我想从 x=0 开始绘制 y 值,而不仅仅是最新的数据点。这些中的任何一个都适用于这个用例吗?此外,您会说从 matplotlib 迁移到 pyqtgraph 还是会导致更流畅的动画?我有大约 20~ 个数字,每个数字都有一个新的 (x,y) 对,每 100-500 毫秒左右绘制一次。 @CompareTwo 听起来您想使用call_in_main。基本上qt_thread_updater 计时器将运行 30 赫兹。当超时发生时,call_latest 只会使用最新的数据集调用给定的函数一次。 call_in_main 将多次调用该函数,并确保为每个数据集调用该函数。你可以试试matplotlib。如果您发现性能问题,我会切换到 pyqtgraph。

以上是关于PyQt5 在不冻结 GUI 的情况下绘制数据的最佳方法 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

如何在不冻结 GUI 的情况下让 AudioQueue 播放?

如何在不冻结 GUI 的情况下使用视频帧不断更新 contentcontrol?

如何在不关闭 GUI 窗口的情况下停止运行 PyQt5 程序?

如何在不冻结 gui 的情况下运行 QProcess 的同步链?

如何在不冻结 GUI 的情况下在单个插槽中实现阻塞进程?

在不终止启动 Python 脚本的情况下关闭 pyqt5 GUI