Search code examples
pythontkinterpython-multithreadingping

after() hangs the output window


I am trying to build a real time project where the status gets updated every second, so some part of code repeats continuously. when i want to change the information which has to be get updated i will just click on new button which gives me the first window where i can update the new information. but by doing so gives me the following error. if i use after() instead of threading, the error wont be there but the output window gets hanged.Please help me with the idea to resolve this. Thank you.

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"

Code where i have problem with:

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 

Sample code:

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()

Solution

  • Main problem is that ping -w 1000 need a lot of time to run but Pool.map() waits for all results. You could even run results = p.map(...) without queue (but with return result) but it could also block tkinter

    You may use map_async() to run it without waiting for results.

    I also use starmap (or rather starmap_async()) to send two arguments ip, queue to every process.

    I also made other changes - ie. rename variables, move some code to __init__ to create only once (images, Pool, Queue). I also send list of IP to other window as argument Window2(master, data) and back Window1(master, data) - so I can edit this list.

    BTW: because I run on Linux so I changed arguments for ping and check different text to test if it get answer.

    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()