首页 > 解决方案 > 我的程序使用了大量的 ram 内存,我该怎么办?

问题描述

我的程序使用了大量的 ram 内存,它的作用是:

  1. 从在线相机下载图像。
  2. 在窗口中显示图像(Tkinter 窗口)
  3. 删除图像。
  4. 再次相同的过程(无限循环,直到窗口关闭)

这是代码:

def show_image(self, cam):
    while True:
        start_time = time.time()
        self.update_image(cam)

        load = Image.open(cam.getPlace() + "/image.jpeg")
        load.thumbnail((400, 300))
        render = ImageTk.PhotoImage(load)

        if cam.getPlace() == "FrontYard1":
            try:
                img1 = Label(image=render)
                img1.image = render
                img1.grid(row=0, column=0)
            except:
                print("Error")
        elif cam.getPlace() == "FrontYard2":
            try:
                img2 = Label(image=render)
                img2.image = render
                img2.grid(row=0, column=1)
            except:
                print("Error")
        elif cam.getPlace() == "Garden1":
            try:
                img3 = Label(image=render)
                img3.image = render
                img3.grid(row=1, column=0)
            except:
                print("Error")
        else:
            try:
                img4 = Label(image=render)
                img4.image = render
                img4.grid(row=1, column=1)
            except:
                print("Error")

        print("And " + str(time.time() - start_time) + " to run show image")

def update_image(self, cam):
    os.remove(cam.getPlace()+"/image.jpeg")
    if cam.getModel() == "Model":
        urllib.request.urlretrieve(#link and folder to download the image)
    else:
        urllib.request.urlretrieve(#link and folder to download the image)

我尝试使用 gc.collect() 但它似乎不起作用。

标签: pythonpython-3.xtkinterram

解决方案


您的代码正在尝试创建无限数量的Label对象,将每个对象隐藏在无限堆叠的其他标签后面。除非您有无限的内存,否则这显然会导致问题。

想要的只是任何给定时间的四个标签。您需要跟踪您创建的标签,理想情况下只是重复使用它们,或者,如果没有,则销毁它们,而不是仅仅将它们隐藏在新标签后面。


img1目前,您的代码通过变量跟踪这些标签中的标签img4,除非它们在第一次显示图像时才会创建,因此您无法检查它们。因此,在循环之前,您要么想要创建四个空标签,要么只是将变量设置为None. 然后,在循环内,您可以这样做(假设您使用None而不是空标签):

    if cam.getPlace() == "FrontYard1":
        try:
            if img1 is None:
                img1 = Label(image=render)
                img1.grid(row=0, column=0)
            else:
                img1.image = render
        # etc.

您可以通过将标签存储在 dict 而不是四个单独的变量中来简化这一点:

imgs = {“Front Yard 1": img1, …}

…然后用 dict 查找替换 if/elif 链:

img = imgs.get(cam.getPlace())

尽管在这里,您几乎肯定会想要使用空标签而不是None初始值。


附带说明一下,您的程序还有另一个非常严重的问题。while True:循环显然永远不会返回到 tkinter 事件循环。这意味着您不会给 tkinter 机会更新显示,或响应鼠标事件或退出或来自窗口系统的其他事件,因此您的应用程序将无响应并弹出沙滩球或沙漏或其他任何东西。

要解决此问题,您需要删除循环,而是让您的方法只检索并处理一个图像,然后调用after以要求 tkinter 下次通过事件循环再次调用它。

这还需要将那些局部img1变量(或上面建议的字典imgs)更改为实例变量。

如果您尝试使用后台线程来执行此任务,则从除主线程之外的任何线程对 tkinter 小部件执行任何操作都是非法的。在某些平台上,这只会立即给您一个错误,或者挂起 GUI。在其他平台上,它似乎可以工作,但每隔一段时间就会做一些奇怪的事情——更糟糕的是。

如果您正在通过例如 Python 3 fork 解决此问题mtTkinter(它包装了Effbot BookQueue中显示的逻辑,因此您在小部件上所做的一切实际上都会发布一条消息供主线程处理),那么您可以忽略本节。但如果没有,您要么这样做,要么手动做同样的事情(如链接页面所示),或者只是摆脱后台线程并使用.after


当我们这样做时,except:仅打印的裸Error机会隐藏任何可能出现的问题并使调试变得更加困难,因此您几乎不应该这样做。

还有,Label.image任务真的是这里唯一可以提的地方吗?


把它们放在一起:

def __init__(self):
    self.imgs = {}
    img1 = Label()
    img1.grid(row=0, column=0)
    self.imgs["Frontyard1"] = img1
    # ... likewise for 2-4

def show_image(self, cam):
    start_time = time.time()

    try:
        self.update_image(cam)

        load = Image.open(cam.getPlace() + "/image.jpeg")
        load.thumbnail((400, 300))
        render = ImageTk.PhotoImage(load)

        img = self.imgs.get(cam.getPlace())
        if img:
            img.image = render
        # What do you want to do if it's not one of the four expected?
        # Your existing code just ignores the image but still counts time,
        # so that's what I did here.

    except Exception as e:
        print(f"Error updating cam '{cam.getPlace()}: {e!r}")
    else:
        print("And " + str(time.time() - start_time) + " to run show image")

    self.root.after(0, self.show_image, cam)

推荐阅读