TkInter越来越多地占用内存

huangapple go评论88阅读模式
英文:

TkInter is hoarding more and more ram

问题

Sure, here are the translated code parts as you requested:

Original Code:

  1. import subprocess
  2. import time
  3. import tkinter as tk
  4. import threading
  5. import gc
  6. ip_addresses = ["192.168.1.83", "192.168.1.93", "192.168.1.93", "192.168.1.92", "192.168.1.95", "192.168.1.103", "192.168.1.105"]
  7. i= 0
  8. if i == 0:
  9. root = tk.Tk()
  10. root.title("Online")
  11. root.geometry("500x300")
  12. i+1
  13. label_texts = [f"{ip_address} -> unknown" for ip_address in ip_addresses]
  14. labels = [tk.Label(root, text=label_text, font=("Arial", 16), fg="#FF0000") for label_text in label_texts]
  15. for i, label in enumerate labels:
  16. label.pack(padx=10, pady=5)
  17. def check_ip_status(ip_address, label):
  18. try:
  19. subprocess.check_output(f"ping {ip_address} -n 1", shell=True, timeout=5)
  20. label.config(text=f"{ip_address} -> online", fg="#00FF00") # Green
  21. except subprocess.CalledProcessError:
  22. label.config(text=f"{ip_address} -> offline", fg="#FF0000") # Red
  23. except subprocess.TimeoutExpired:
  24. label.config(text=f"{ip_address} -> offline (timeout)", fg="#FFA500") # Orange
  25. def check_all_ip_statuses():
  26. processes = []
  27. for i, ip_address in enumerate(ip_addresses):
  28. label = labels[i]
  29. process = subprocess.Popen(f"ping {ip_address} -t", shell=True, stdout=subprocess.DEVNULL)
  30. processes.append(process)
  31. thread = threading.Thread(target=check_ip_status, args=(ip_address, label))
  32. thread.start()
  33. root.after(5000, check_all_ip_statuses)
  34. gc.collect()
  35. # Close all subprocesses
  36. for process in processes:
  37. process.kill()
  38. process.communicate()
  39. check_all_ip_statuses()
  40. root.mainloop()

Updated Code:

  1. import pythonping
  2. import time
  3. import tkinter as tk
  4. import threading
  5. import gc
  6. ip_addresses = ["192.168.1.83", "192.168.1.85", "192.168.1.93", "192.168.1.92", "192.168.1.95", "192.168.1.103", "192.168.1.105"]
  7. i= 0
  8. if i == 0:
  9. root = tk.Tk()
  10. root.title("Online")
  11. root.geometry("500x300")
  12. i+1
  13. def create_labels():
  14. global labels
  15. labels = [tk.Label(root, text=f"{ip_address} -> unknown", font=("Arial", 16), fg="#FF0000") for ip_address in ip_addresses]
  16. for label in labels:
  17. label.pack(padx=10, pady=5)
  18. def check_ip_status(ip_address, label):
  19. response = pythonping.ping(ip_address, count=1, timeout=5)
  20. if response.success():
  21. label.config(text=f"{ip_address} -> online", fg="#00FF00") # Green
  22. else:
  23. label.config(text=f"{ip_address} -> offline", fg="#FF0000") # Red
  24. def check_all_ip_statuses():
  25. for i, ip_address in enumerate(ip_addresses):
  26. label = labels[i]
  27. thread = threading.Thread(target=check_ip_status, args=(ip_address, label))
  28. thread.start()
  29. gc.collect()
  30. root.after(1000, check_all_ip_statuses)
  31. create_labels()
  32. check_all_ip_statuses()
  33. root.mainloop()
英文:

I am creating a script to check the status of some of my computers but noticed that over time the Tkinter window is accumulating more and more resources. Is there any way to fix this?

  1. import subprocess
  2. import time
  3. import tkinter as tk
  4. import threading
  5. import gc
  6. ip_addresses = ["192.168.1.83", "192.168.1.93", "192.168.1.93", "192.168.1.92", "192.168.1.95", "192.168.1.103", "192.168.1.105"]
  7. i= 0
  8. if i == 0:
  9. root = tk.Tk()
  10. root.title("Online")
  11. root.geometry("500x300")
  12. i+1
  13. label_texts = [f"{ip_address} -> unknown" for ip_address in ip_addresses]
  14. labels = [tk.Label(root, text=label_text, font=("Arial", 16), fg="#FF0000") for label_text in label_texts]
  15. for i, label in enumerate(labels):
  16. label.pack(padx=10, pady=5)
  17. def check_ip_status(ip_address, label):
  18. try:
  19. subprocess.check_output(f"ping {ip_address} -n 1", shell=True, timeout=5)
  20. label.config(text=f"{ip_address} -> online", fg="#00FF00") # Green
  21. except subprocess.CalledProcessError:
  22. label.config(text=f"{ip_address} -> offline", fg="#FF0000") # Red
  23. except subprocess.TimeoutExpired:
  24. label.config(text=f"{ip_address} -> offline (timeout)", fg="#FFA500") # Orange
  25. def check_all_ip_statuses():
  26. processes = []
  27. for i, ip_address in enumerate(ip_addresses):
  28. label = labels[i]
  29. process = subprocess.Popen(f"ping {ip_address} -t", shell=True, stdout=subprocess.DEVNULL)
  30. processes.append(process)
  31. thread = threading.Thread(target=check_ip_status, args=(ip_address, label))
  32. thread.start()
  33. root.after(5000, check_all_ip_statuses)
  34. gc.collect()
  35. # Close all subprocesses
  36. for process in processes:
  37. process.kill()
  38. process.communicate()
  39. check_all_ip_statuses()
  40. root.mainloop()

I've tried to force the garbage collector, kill all processes and to clear the labels every so often but nothing stops tkinter from hoarding more and more ram

-------------Update---------

I've changed the subprocess call to pythonping

  1. import pythonping
  2. import time
  3. import tkinter as tk
  4. import threading
  5. import gc
  6. ip_addresses = ["192.168.1.83", "192.168.1.85", "192.168.1.93", "192.168.1.92", "192.168.1.95", "192.168.1.103", "192.168.1.105"]
  7. i= 0
  8. if i == 0:
  9. root = tk.Tk()
  10. root.title("Online")
  11. root.geometry("500x300")
  12. i+1
  13. def create_labels():
  14. global labels
  15. labels = [tk.Label(root, text=f"{ip_address} -> unknown", font=("Arial", 16), fg="#FF0000") for ip_address in ip_addresses]
  16. for label in labels:
  17. label.pack(padx=10, pady=5)
  18. def check_ip_status(ip_address, label):
  19. response = pythonping.ping(ip_address, count=1, timeout=5)
  20. if response.success():
  21. label.config(text=f"{ip_address} -> online", fg="#00FF00") # Green
  22. else:
  23. label.config(text=f"{ip_address} -> offline", fg="#FF0000") # Red
  24. def check_all_ip_statuses():
  25. for i, ip_address in enumerate(ip_addresses):
  26. label = labels[i]
  27. thread = threading.Thread(target=check_ip_status, args=(ip_address, label))
  28. thread.start()
  29. gc.collect()
  30. root.after(1000, check_all_ip_statuses)
  31. create_labels()
  32. check_all_ip_statuses()
  33. root.mainloop()

答案1

得分: 1

我尝试找出你的版本为什么会占用内存,但我还没搞清楚。现在我找不到如何以实际方式使用所有内存分析工具来找出源代码中的问题。

所以,我创建了另一个版本的你的应用,我采用了面向对象的方法。

  1. import sys
  2. import threading
  3. import time
  4. import tkinter
  5. import pythonping
  6. class PingHost(threading.Thread):
  7. def __init__(self, ip, idx):
  8. super().__init__()
  9. self.daemon = True
  10. self.start_time = time.time()
  11. self.ip = ip
  12. self.idx = idx
  13. self.message = ''
  14. self.success = False
  15. def run(self) -> None:
  16. result = pythonping.ping(self.ip, count=1, timeout=5)
  17. if result.success():
  18. self.message = f'{self.ip} -> online'
  19. self.success = True
  20. else:
  21. self.message = f'{self.ip} -> offline'
  22. self.success = False
  23. class App(tkinter.Frame):
  24. IP_ADDRESSES = [
  25. '192.168.80.101', '192.168.80.111', '192.168.80.123', '192.168.80.124', '192.168.80.125', '192.168.80.126',
  26. '192.168.80.127', '192.168.80.128'
  27. ]
  28. COLOR_ONLINE = '#00FF00'
  29. COLOR_OFFLINE = '#FF0000'
  30. FONT = ('Arial', 16)
  31. PING_THROTTLE = 10 # delay until a ping thread restarts
  32. def __init__(self, tk_root: tkinter.Tk):
  33. super().__init__(tk_root)
  34. self.root = tk_root
  35. self.root.protocol('WM_DELETE_WINDOW', self._on_quit)
  36. self.labels: [tkinter.Label] = []
  37. self.procs: [threading.Thread] = []
  38. self._create_widgets()
  39. self._ping_all_ips()
  40. self._monitor_procs()
  41. def _create_widgets(self):
  42. for ip in self.IP_ADDRESSES:
  43. string = f'{ip} -> unknown'
  44. label = tkinter.Label(self.root, text=string, fg=self.COLOR_OFFLINE, font=self.FONT)
  45. label.pack()
  46. self.labels.append(label)
  47. def _ping_all_ips(self):
  48. for idx, ip in enumerate(self.IP_ADDRESSES):
  49. self._start_ping_proc(ip, idx)
  50. def _start_ping_proc(self, ip, idx):
  51. ping_proc = PingHost(ip, idx)
  52. ping_proc.start()
  53. self.procs.append(ping_proc)
  54. def _monitor_procs(self):
  55. proc: PingHost
  56. for proc in self.procs:
  57. age = time.time() - proc.start_time
  58. if proc.is_alive():
  59. continue
  60. if age < self.PING_THROTTLE:
  61. self._update_labels(proc)
  62. continue
  63. self._update_labels(proc)
  64. self.procs.remove(proc)
  65. self._start_ping_proc(proc.ip, proc.idx)
  66. self.root.after(1000, self._monitor_procs)
  67. def _update_labels(self, proc):
  68. if "online" in proc.message:
  69. self.labels[proc.idx].configure(text=proc.message, fg=self.COLOR_ONLINE)
  70. else:
  71. self.labels[proc.idx].configure(text=proc.message, fg=self.COLOR_OFFLINE)
  72. def _on_quit(self):
  73. self.root.quit()
  74. sys.exit()
  75. if __name__ == '__main__':
  76. root = tkinter.Tk()
  77. root.title('oop threading test')
  78. root.geometry('400x400')
  79. app = App(root)
  80. root.mainloop()

昨晚整个夜晚都在运行,内存一直维持在约12MB左右。
我有很多理论可以解释为什么这个版本能运行而你的版本不行,但目前我无法证明其中任何一个理论。

如果有人能给予启示,将不胜感激。

如果我发现更多信息,我会更新。

英文:

I tried to find out why you version is filling up memory, but I'm not there yet.
Right now I couldn't find out how to use all the memory analyzing tools in a practical way to find the culprit in your source.

So, I created another version of your app, the way I would do it using an OOP approach.

  1. import sys
  2. import threading
  3. import time
  4. import tkinter
  5. import pythonping
  6. class PingHost(threading.Thread):
  7. def __init__(self, ip, idx):
  8. super().__init__()
  9. self.daemon = True
  10. self.start_time = time.time()
  11. self.ip = ip
  12. self.idx = idx
  13. self.message = &#39;&#39;
  14. self.success = False
  15. def run(self) -&gt; None:
  16. result = pythonping.ping(self.ip, count=1, timeout=5)
  17. if result.success():
  18. self.message = f&#39;{self.ip} -&gt; online&#39;
  19. self.success = True
  20. else:
  21. self.message = f&#39;{self.ip} -&gt; offline&#39;
  22. self.success = False
  23. class App(tkinter.Frame):
  24. IP_ADDRESSES = [
  25. &#39;192.168.80.101&#39;, &#39;192.168.80.111&#39;, &#39;192.168.80.123&#39;, &#39;192.168.80.124&#39;, &#39;192.168.80.125&#39;, &#39;192.168.80.126&#39;,
  26. &#39;192.168.80.127&#39;, &#39;192.168.80.128&#39;
  27. ]
  28. COLOR_ONLINE = &#39;#00FF00&#39;
  29. COLOR_OFFLINE = &#39;#FF0000&#39;
  30. FONT = (&#39;Arial&#39;, 16)
  31. PING_THROTTLE = 10 # delay until a ping thread restarts
  32. def __init__(self, tk_root: tkinter.Tk):
  33. super().__init__(tk_root)
  34. self.root = tk_root
  35. self.root.protocol(&#39;WM_DELETE_WINDOW&#39;, self._on_quit)
  36. self.labels: [tkinter.Label] = []
  37. self.procs: [threading.Thread] = []
  38. self._create_widgets()
  39. self._ping_all_ips()
  40. self._monitor_procs()
  41. def _create_widgets(self):
  42. for ip in self.IP_ADDRESSES:
  43. string = f&#39;{ip} -&gt; unknown&#39;
  44. label = tkinter.Label(self.root, text=string, fg=self.COLOR_OFFLINE, font=self.FONT)
  45. label.pack()
  46. self.labels.append(label)
  47. def _ping_all_ips(self):
  48. for idx, ip in enumerate(self.IP_ADDRESSES):
  49. self._start_ping_proc(ip, idx)
  50. def _start_ping_proc(self, ip, idx):
  51. ping_proc = PingHost(ip, idx)
  52. ping_proc.start()
  53. self.procs.append(ping_proc)
  54. def _monitor_procs(self):
  55. proc: PingHost
  56. for proc in self.procs:
  57. age = time.time() - proc.start_time
  58. if proc.is_alive():
  59. continue
  60. if age &lt; self.PING_THROTTLE:
  61. self._update_labels(proc)
  62. continue
  63. self._update_labels(proc)
  64. self.procs.remove(proc)
  65. self._start_ping_proc(proc.ip, proc.idx)
  66. self.root.after(1000, self._monitor_procs)
  67. def _update_labels(self, proc):
  68. if &quot;online&quot; in proc.message:
  69. self.labels[proc.idx].configure(text=proc.message, fg=self.COLOR_ONLINE)
  70. else:
  71. self.labels[proc.idx].configure(text=proc.message, fg=self.COLOR_OFFLINE)
  72. def _on_quit(self):
  73. self.root.quit()
  74. sys.exit()
  75. if __name__ == &#39;__main__&#39;:
  76. root = tkinter.Tk()
  77. root.title(&#39;oop threading test&#39;)
  78. root.geometry(&#39;400x400&#39;)
  79. app = App(root)
  80. root.mainloop()

This was running the whole night yesterday, with memory constantly around 12MB
I've got a lot of theories why this is working and yours is not, but I can't proove any of it atm.

So if anyone can shed light, it would be highly appreciated.

I'll update if I find more.

答案2

得分: 0

以下是您要求的代码部分的中文翻译:

  1. 这个版本是您的代码带有一些更新
  2. 我认为我终于理解了您添加的部分

i = 0
if i == 0:
root = tk.Tk()
root.title("Online")
root.geometry("500x300")
i+1

  1. 这是不必要的,因为您的主应用程序不会重复执行。`root.mainloop()`实际上是一个循环,但不会一遍又一遍地重新启动您的应用程序,因此可以删除它。
  2. 至于您关于`if __name__ == '__main__':`的注释可以修复它,实际上不会。这是为了防止您的脚本在被导入时执行。

print('如果被导入,我将打印')

if name == 'main':
print('如果被导入,这将不会打印,但如果作为脚本执行,则会打印')

  1. 我创建了一个扩展了`threading.Thread``Ping`类,以便我可以向线程添加属性,然后运行它。
  2. 与您的代码最大的不同之处在于,我在`check_all_ip_statuses`函数中检查所有正在运行的线程,如果线程未完成,我将继续执行。如果线程已完成,我将读取`response`属性以更新标签。
  3. 这样标签不是从线程内部更新的,似乎可以解决内存泄漏问题。
  4. ```python
  5. from typing import List
  6. import pythonping
  7. import tkinter as tk
  8. import threading
  9. class Ping(threading.Thread):
  10. def __init__(self, ip_address, idx):
  11. super().__init__()
  12. self.daemon = True
  13. self.ip_address = ip_address
  14. self.idx = idx
  15. self.response: bool = False
  16. def run(self):
  17. response = pythonping.ping(self.ip_address, count=1, timeout=5)
  18. self.response = response.success()
  19. def create_labels():
  20. global labels
  21. labels = [tk.Label(root, text=f"{ip_address} -> 未知", font=("Arial", 16), fg="#FF0000") for ip_address in ip_addresses]
  22. for label in labels:
  23. label.pack(padx=10, pady=5)
  24. def start_threads():
  25. for i, ip_address in enumerate(ip_addresses):
  26. thread = Ping(ip_address, i)
  27. thread.start()
  28. threads.append(thread)
  29. def check_all_ip_statuses():
  30. for thread in threads:
  31. if thread.is_alive():
  32. continue
  33. if thread.response:
  34. labels[thread.idx].config(text=f"{thread.ip_address} -> 在线", fg="#00FF00") # 绿色
  35. else:
  36. labels[thread.idx].config(text=f"{thread.ip_address} -> 离线", fg="#FF0000") # 红色
  37. new_thread = Ping(thread.ip_address, thread.idx)
  38. new_thread.start()
  39. threads.remove(thread)
  40. threads.append(new_thread)
  41. root.after(1000, check_all_ip_statuses)
  42. ip_addresses = ["192.168.1.83", "192.168.1.85", "192.168.1.93", "192.168.1.92", "192.168.1.95", "192.168.1.103",
  43. "192.168.1.105"]
  44. labels: List[tk.Label]
  45. threads: List[Ping] = []
  46. root = tk.Tk()
  47. root.title("Online")
  48. root.geometry("500x300")
  49. create_labels()
  50. start_threads()
  51. check_all_ip_statuses()
  52. root.mainloop()

正如@ShadowRanger在您的问题评论中提到的,关于是否可以从线程中更新标签:

@TimRoberts: 嗯,有时候可以。然后有时候一切都会崩溃。你不应该这样做,但人们有时会变懒,有时会奏效,直到它不奏效。

看起来他是对的。

英文:

So this version is your code, with some updates.

I think I finally understand this part you added:

  1. i= 0
  2. if i == 0:
  3. root = tk.Tk()
  4. root.title(&quot;Online&quot;)
  5. root.geometry(&quot;500x300&quot;)
  6. i+1

This is not needed, as your main application does not repeat itself. The root.mainloop() is in fact a loop in itself, but does not restart your application over and over again so you can remove it.

As for your comment about if __name__ == &#39;__main__&#39;: would fix it, not it would not. This is meant to prevent your script from executing if it is imported.

  1. print(&#39;I will print if this gets imported&#39;)
  2. if __name__ == &#39;__main__&#39;:
  3. print(&#39;this will not print, if this gets imported, but will if it is executed as a script&#39;)

I created a Ping class that extends threading.Thread so I can add attributes to the thread and then run it.

So the main thing that is different form your code, is that I check all running threads in the check_all_ip_statuses function and just continue if the thread is not finished. If it is finished, I'll read out the response attribute to update the lables.

This way the labels are not updated from inside the thread and this seems to fix the memory leak problem.

  1. from typing import List
  2. import pythonping
  3. import tkinter as tk
  4. import threading
  5. class Ping(threading.Thread):
  6. def __init__(self, ip_address, idx):
  7. super().__init__()
  8. self.daemon = True
  9. self.ip_address = ip_address
  10. self.idx = idx
  11. self.response: bool = False
  12. def run(self):
  13. response = pythonping.ping(self.ip_address, count=1, timeout=5)
  14. self.response = response.success()
  15. def create_labels():
  16. global labels
  17. labels = [tk.Label(root, text=f&quot;{ip_address} -&gt; unknown&quot;, font=(&quot;Arial&quot;, 16), fg=&quot;#FF0000&quot;) for ip_address in
  18. ip_addresses]
  19. for label in labels:
  20. label.pack(padx=10, pady=5)
  21. def start_threads():
  22. for i, ip_address in enumerate(ip_addresses):
  23. thread = Ping(ip_address, i)
  24. thread.start()
  25. threads.append(thread)
  26. def check_all_ip_statuses():
  27. for thread in threads:
  28. if thread.is_alive():
  29. continue
  30. if thread.response:
  31. labels[thread.idx].config(text=f&quot;{thread.ip_address} -&gt; online&quot;, fg=&quot;#00FF00&quot;) # Green
  32. else:
  33. labels[thread.idx].config(text=f&quot;{thread.ip_address} -&gt; offline&quot;, fg=&quot;#FF0000&quot;) # Red
  34. new_thread = Ping(thread.ip_address, thread.idx)
  35. new_thread.start()
  36. threads.remove(thread)
  37. threads.append(new_thread)
  38. root.after(1000, check_all_ip_statuses)
  39. ip_addresses = [&quot;192.168.1.83&quot;, &quot;192.168.1.85&quot;, &quot;192.168.1.93&quot;, &quot;192.168.1.92&quot;, &quot;192.168.1.95&quot;, &quot;192.168.1.103&quot;,
  40. &quot;192.168.1.105&quot;]
  41. labels: List[tk.Label]
  42. threads: List[Ping] = []
  43. root = tk.Tk()
  44. root.title(&quot;Online&quot;)
  45. root.geometry(&quot;500x300&quot;)
  46. create_labels()
  47. start_threads()
  48. check_all_ip_statuses()
  49. root.mainloop()

As @ShadowRanger mentioned in one of the comments of your question, about if it is okay to update labels from a thread:

@TimRoberts: Well, you can. Sometimes. And then sometimes everything breaks. You shouldn&#39;t do it, but people get lazy and sometimes it works, right up until it doesn&#39;t.

Seems like he is right.

huangapple
  • 本文由 发表于 2023年4月4日 05:21:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/75923865.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定