首页 > 解决方案 > 正确关闭函数调用后使用的 Tkinter

问题描述

现在我使用 Tkinter 作为前端框架来显示通过队列传入的数据。然后使用我的 draw() 函数等将这些数据绘制到屏幕上。

循环部分在 n 毫秒后调用特定函数的函数后使用 Tkinter。我有一种感觉,当调用销毁或只是关闭窗口时,这个循环会停止?导致一些后端进程不满足。

我已经在下面发布了代码,因为您将缺少抽象类。删除init中的 (Updater) 和 super将满足它,因为抽象类更像是一个接口。

重现问题:在脚本运行期间,如果 Tkinter 窗口在将队列中的所有数据绘制到绘图之前以任何可用方式关闭。该脚本永远不会返回到命令行并且永远卡住。这种情况是不利的,因为能够退出窗口并期望进程被破坏是我所寻求的。

进一步追踪: oscy.run() 传递失败,程序立即退出但不返回命令行;正如在完成之前运行并迅速退出程序所看到的那样。这开始暗示init中发生了什么?

from __future__ import division

import logging

import atexit
import matplotlib
import numpy as np
matplotlib.use('TkAgg')

from functools import wraps
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from Tkinter import Scale, Button, Tk, TOP, BOTTOM, BOTH, HORIZONTAL


class Oscilloscope():
    """ Displays the Oscilloscope provided the data streaming in. """
    def __init__(
        self, 
        data_queue,
        closed_callback,
        title="DRP",
        xlabel="Range Cells",
        ylabel="Magnitude"
    ):
        """
        Initialization function for the Osc oscilloscope.
        :param data_queue: List data representative of magnitudes per update.
        :type data_queue: Queue
        :param closed_callback: When the figure is closed, callback should be used to remove the figure.
        :type closed_callback: Function
        :param title: Title of the plot being drawn for the Oscilloscope.
        :type title: String
        :param xlabel: X-axis of the plot being drawn, should either be Range or Doppler.
        :type xlabel: String
        :param ylabel: Y-axis of the plot being drawn, should always be Magnitude.
        :type ylabel: String
        """

        self.data_queue = data_queue
        self.closed_callback = closed_callback

        self.window = Tk()
        atexit.register(self.closed_callback)

        self.title = title
        self.xlabel = xlabel
        self.ylabel = ylabel

        self.y_limits = np.array([0, np.finfo(np.float).eps])      

    def _adjust_ylim_if_req(self, magnitude):
        """
        Changes the limits based on magnitudes. 
        :param magnitude: Size of the magnitude being plotted.
        :type magnitude: Float
        """
        if magnitude < self.y_limits[0]:
            self.y_limits[0] = magnitude
        elif magnitude > self.y_limits[1]:
            self.y_limits[1] = magnitude
        self.ax.set_ylim(self.y_limits[0], self.y_limits[1])

    def draw(self):
        """
        Draws the main line plot.
        """ 
        try:
            magnitudes = self.data_queue.get_nowait()
        except:
            pass
        else:
            # Adjust y limits
            for magnitude in magnitudes:
                self._adjust_ylim_if_req(magnitude)
            # Plot the graph
            self.ax.cla()
            self.ax.set_title(self.title, fontdict={'fontsize': 16, 'fontweight': 'medium'})
            self.ax.set_xlabel(self.xlabel, fontdict={'fontsize': 12, 'fontweight': 'medium'})
            self.ax.set_ylabel(self.ylabel, fontdict={'fontsize': 12, 'fontweight': 'medium'})
            self.ax.plot([n for n in range(len(magnitudes))], magnitudes, '-bo')

    def run(self):
        """
        Sets up and runs the main logic of the Window
        """
        self.plot()
        self.updateplot()
        self.window.mainloop()

    def plot(self):
        """
        Creates the initial base plot
        """
        figure = matplotlib.figure.Figure()
        self.ax = figure.add_subplot(1,1,1)
        self.ax.set_title(self.title, fontdict={'fontsize': 16, 'fontweight': 'medium'})
        self.ax.set_xlabel(self.xlabel, fontdict={'fontsize': 12, 'fontweight': 'medium'})
        self.ax.set_ylabel(self.ylabel, fontdict={'fontsize': 12, 'fontweight': 'medium'})
        self.canvas = FigureCanvasTkAgg(figure, master=self.window)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
        self.canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=1)

        self.draw()

        def close_fig():
            self.window.destroy()
            self.closed_callback
        button = Button(self.window, text='Close', command=close_fig)
        button.pack()

    def updateplot(self):
        """
        Updates the plot by gathering more data from the Queue.
        """
        print('Start')
        self.draw()
        self.canvas.draw()
        self.window.after(1, self.updateplot)
        print('End')

if __name__ == '__main__':
    from threading import Thread
    from multiprocessing import Queue
    q = Queue()
    for i in xrange(100):
        l = []
        for i in xrange(1000):
            l.append(np.random.randint(0, 100))
        q.put(l)
    def cb():
        print('Closed')
    oscy = Oscilloscope(q, cb)
    oscy.run()

在此处输入图像描述

标签: pythonpython-2.7tkinter

解决方案


这是一个非常奇怪的问题,我永远不会想到。

解决方案

使用 multiprocessing.Queue 是问题所在,要解决此问题,请使用常规队列。

发生了什么

多处理队列似乎通过管道输入它的数据。当程序终止并且 main 的范围丢失时,这些数据仍然被持有,等待被删除。然后这会导致“挂起”,即什么都没有发生,但在后台发生了一些事情。

如何在python中清除多处理队列

在程序执行之前调用 close() 可以解决问题。由于多处理模块本身就是一个基于进程的“线程”接口。


推荐阅读