after() 挂起输出 window

after() hangs the output window

我正在尝试构建一个实时项目,其中的状态每秒都会更新,因此部分代码会不断重复。当我想更改必须更新的信息时,我只需单击新按钮,这会给我第一个 window,我可以在其中更新新信息。但这样做会给我以下错误。如果我使用 after() 而不是线程,则不会出现错误,但输出 window 得到 hanged.Please 帮助我有解决这个问题的想法。谢谢。

Exception in thread Thread-2:
    Traceback (most recent call last):
      File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\threading.py", line 926, in _bootstrap_inner
        self.run()
      File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\threading.py", line 1177, in run
        self.function(*self.args, **self.kwargs)
      File "C:/Users/Desktop/Tool/t.py", line 47, in ae
        self.treeview.insert('', 'end',image=self._img, value=(a))
      File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\tkinter\ttk.py", line 1370, in insert
        res = self.tk.call(self._w, "insert", parent, index, *opts)
    _tkinter.TclError: invalid command name ".!treeview"

我遇到问题的代码:

def aaa(self):
                num_threads = 5 * multiprocessing.cpu_count()
                p = multiprocessing.dummy.Pool(num_threads)
                p.map(self.ping_func, [x for x in Demo2.t1])
                self.process_incoming() 
                #threading.Timer(1.0, self.aaa).start()-this gives the error while pressing new button and updating information
                self.master.after(100, self.aaa) #it hangs the output window 

示例代码:

import multiprocessing.dummy
import multiprocessing
import os
import socket
import sys
import subprocess
import re
import time
import threading
import tkinter.messagebox
from tkinter import ttk
import queue
from tkinter import *

class Demo1:  #window 1
    data=[]
    def __init__(self, master):
        self.master = master
        self.t=tkinter.Text(self.master,height=20,width=50)
        self.t.grid(row=1, column=1)
        self.button = tkinter.Button(self.master,height=3,width=10, text="OK", command = self.new_window)
        self.button.grid(row=2,column=1)

    def new_window(self):
        self.inputValue=self.t.get("1.0",'end-1c')
        Demo1.data=self.inputValue.split("\n")
        self.master.destroy() # close the current window
        self.master = tkinter.Tk() # create another Tk instance
        self.app = Demo2(self.master) # create Demo2 window
        self.master.mainloop()

class Demo2: #window 2
    value = []
    display = []
    num=0
    def __init__(self, master):
        self.master = master
        self.queue = queue.Queue()
        Demo2.value = Demo1.data
        self.button = tkinter.Button(self.master,height=2,width=11, text="new",command=self.new).place(x=0,y=0)
        self.label = tkinter.Label(self.master, text="monitor", font=("Arial",20)).grid(row=0, columnspan=3)
        cols = ('aa','bb')
        self.treeview = ttk.Treeview(self.master, columns=cols)
        for col in cols:
            self.treeview.heading(col, text=col)
            self.treeview.column(col,minwidth=0,width=170)
        self.treeview.grid(row=1, column=0)
        self._img=tkinter.PhotoImage(file="green1.gif")
        self.aaa()
        
    def aaa(self):
                num_threads = 5 * multiprocessing.cpu_count()
                p = multiprocessing.dummy.Pool(num_threads)
                p.map(self.ping_func, [x for x in Demo2.value])
                self.process_incoming() 
                #threading.Timer(1.0, self.aaa).start()
                self.master.after(100, self.aaa)
                
    def ping_func(self,ip):   #Ping every ip and append the result 
            ping_result = []
            pingCmd = "ping -n 1 -w 1000 " + ip
            childStdout = os.popen(pingCmd)
            result = (childStdout.readlines())
            childStdout.close()
            ping_result.append(ip)
            if(any('Reply from' in i for i in result)):
                ping_result.append("success")
            else:
                ping_result.append("failed")
            self.queue.put(ping_result)  #Thread value to queue
            
    def process_incoming(self):   #add the ping result to treeview
        while self.queue.qsize():
            try:
                if Demo2.num<len(Demo1.data):
                    self._img=tkinter.PhotoImage(file="green1.gif")
                    self._img1=tkinter.PhotoImage(file="red.gif")
                    msg = self.queue.get_nowait()
                    Demo2.display.append(msg)  #adding queue value to variable(display)
                    if(len(Demo2.display)==len(Demo1.data)):      
                        self.treeview.insert("","end",values=(0,0,0,0,0))
                        self.treeview.delete(*self.treeview.get_children())
                        for i,(a,b) in enumerate(Demo2.display):
                            if(Demo2.display[i][1]=='success' ):
                                self.treeview.insert('', 'end',image=self._img, value=(a,b))
                            else:
                                self.treeview.insert('', 'end',image=self._img1, value=(a,b))
                        Demo2.num=Demo2.num+1
                        Demo2.display.clear()
                else:
                    Demo2.display.clear()
                    Demo2.num=0     
            except queue.Empty:  # Shouldn't happen.
                pass
            
    def periodic_call(self):
        self.master.after(200, self.periodic_call) # checking its contents periodically
        self.process_incoming()
        if not self.running:
            import sys
            sys.exit(1)       

    def new(self):
        self.master.destroy() # close the current window
        self.master = tkinter.Tk() # create another Tk instance
        self.app = Demo1(self.master) # create Demo2 window
        self.master.mainloop()

def main():
    root = tkinter.Tk()
    app = Demo1(root)
    root.mainloop()


if __name__ == '__main__':
    main()

主要问题是 ping -w 1000 需要很多时间 运行 但 Pool.map() 等待所有结果。你甚至可以 运行 results = p.map(...) 没有 queue (但有 return result)但它也可以阻止 tkinter

您可以使用 map_async() 到 运行 它而无需等待结果。

我还使用 starmap(或者 starmap_async())向每个进程发送两个参数 ip, queue

我还进行了其他更改 - 即。重命名变量,将一些代码移动到 __init__ 以仅创建一次(图像、PoolQueue)。我还将 IP 列表作为参数 Window2(master, data) 发送给其他 window 并返回 Window1(master, data) - 所以我可以编辑此列表。

BTW: 因为我在 Linux 上 运行 所以我更改了 ping 的参数并检查不同的文本以测试它是否得到答案。

import os
import multiprocessing.dummy
import queue
import tkinter as tk
import tkinter.ttk as ttk

# --- classes ---

class Window1:
    
    def __init__(self, master, data=None):
        self.master = master
        
        self.data = data
        
        if self.data is None:
            self.data = []

        self.text = tk.Text(self.master, height=20, width=50)
        self.text.grid(row=1, column=1)
        
        self.button = tk.Button(self.master, height=3, width=10, text="OK", command=self.new_window)
        self.button.grid(row=2, column=1)

        # put self.data in Text
        #for item in self.data:
        #    self.text.insert('end', item + '\n')
        self.text.insert('end', '\n'.join(self.data))
            
    def new_window(self):
        text = self.text.get('1.0', 'end')
        
        # remove empty lines
        self.data = [item.strip() for item in text.split("\n") if item.strip()]
        
        self.master.destroy()
        
        root = tk.Tk()
        Window2(root, self.data)
        root.mainloop()
        
        
class Window2:
    
    def __init__(self, master, data):
        self.master = master

        # keep list
        self.data = data
        
        # create dictionary for results
        self.results = {ip: [ip, '???'] for ip in self.data}
        
        self.button = tk.Button(self.master, height=2, width=11, text='New', command=self.new)
        self.button.grid(row=0, column=0, sticky='w')

        self.label = tk.Label(self.master, text='monitor', font=("Arial", 20))
        self.label.grid(row=0, column=1)

        cols = ('IP','Result')
        
        self.treeview = ttk.Treeview(self.master, columns=cols)
        for col in cols:
            self.treeview.heading(col, text=col)
            self.treeview.column(col,minwidth=0,width=170)
        self.treeview.grid(row=1, column=0, columnspan=3)
        
        # create only once
        self._image_green = None # tk.PhotoImage(file="green1.gif")
        self._image_red   = None # tk.PhotoImage(file="red.gif")

        # to reduce number of processes for small `data`
        n = min(5, len(self.data))

        # create only once
        self.queue = queue.Queue()
        self.num_threads = n * multiprocessing.cpu_count()
        self.p = multiprocessing.dummy.Pool(self.num_threads)

        # to stop `after()`
        self.running = True        

        # run first time
        self.update_treeview()  # to display it before running processes

        self.run_processes()
        self.processes_incoming() # first create window to display it faster
        
    def run_processes(self):
        if self.running:
            self.p.starmap_async(self.ping, [(ip, self.queue) for ip in self.data])
            self.after_ID2 = self.master.after(500, self.run_processes)
        
    def ping(self, ip, queue):
        #print('start ping:', ip)
        
        #cmd = 'ping -n 1 -w 3 ' + ip
        cmd = 'ping -w 1 ' + ip  # Linux
        
        child_stdout = os.popen(cmd)
        result = child_stdout.readlines()
        child_stdout.close()
        
        #print('end ping:', ip)

        #if any('Reply from' in line for line in result):
        if any('bytes from' in line for line in result): # Linux
            value = [ip, 'success']
        else:
            value = [ip, 'failed']
            
        queue.put(value)
        
    def update_treeview(self):
        self.treeview.delete(*self.treeview.get_children())
        
        for ip in self.data:
            ip, value = self.results[ip]
            if value == 'success':
                image = self._image_green
            elif value == 'failed':
                image = self._image_red
            else:
                image = None

            #self.treeview.insert('', 'end', image=image, value=(ip, valueb))
            self.treeview.insert('', 'end', value=(ip, value))

    def processes_incoming(self):
       
       if self.running:

            # get all from queue
            new_values = False
            while self.queue.qsize():
            #while not self.queue.empty:
                data = self.queue.get_nowait()
                ip, value = data
                self.results[ip] = data
                new_values = True

            # update only if new values    
            if new_values:
                self.update_treeview()
                
            # repeate after 100ms    
            self.after_ID1 = self.master.after(100, self.processes_incoming) 

    def new(self):
        # to stop all `after()`
        self.running = False
        self.master.after_cancel(self.after_ID1)
        self.master.after_cancel(self.after_ID2)
        
        self.master.destroy()
        
        root = tk.Tk()
        Window1(root, self.data)
        root.mainloop()

# --- functions ---

def main():
    examples = [
        '127.0.0.1',    # localhost
        '10.0.0.1',     # IP in local network
        '192.168.0.1',  # IP in local network
        '8.8.8.8',      # Google DNS
        '8.8.4.4',      # Google DNS
    ]
    
    root = tk.Tk()
    Window1(root, examples)
    root.mainloop()

# --- main ---

if __name__ == '__main__':
    main()