0

I am trying to ping and get the result of 100+ IPs within 1-2 sec.But running this code using Tkinter is giving me the following error please help me with this.

RuntimeError: main thread is not in main loop

Attaching the code. Please have a look at it and let me know if any other information is needed

Thank you.

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

class Demo1:
    data=[]
    def __init__(self, master):
        self.master = master
        self.label=tkinter.Label(text="Add IP/Hostname")
        self.label.pack()
        self.t=tkinter.Text(self.master,height=20,width=50)
        self.t.pack()
        self.button = tkinter.Button(self.master,height=3,width=10, text="OK", command = self.new_window)
        self.button.pack()
        
    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.configure(bg='#6EBFE4')
        self.master.mainloop()
class Demo2(Demo1):
    t1=[]
    s1=True
    display=[]
    def __init__(self, master):
        self.master=master
        self.kas(master)
    def kas(self,master):
        Demo2.t1=Demo1.data
        self.master = master        
        cols = ('IP','Ping status')
        self.listBox = ttk.Treeview(self.master, columns=cols)
        for col in cols:
            self.listBox.heading(col, text=col)
            self.listBox.column(col,minwidth=0,width=170)
        self.listBox.column('#0',width=50)
        self.listBox.grid(row=1, column=0, columnspan=2)
        self.ping_range(Demo2.t1)

    def ping_func(self,ip):
        p=[]
        pingCmd = "ping -n 1 -w 1000 " + ip
        childStdout = os.popen(pingCmd)
        result = (childStdout.readlines())
        childStdout.close()
        p.append(ip)
        if (any('Reply from' in i for i in result)) and (any('Destination host unreachable' not in i for i in result)):
           p.append("sucess")
        else:
           p.append("failed")
        for i,(a) in enumerate(p):
            self.listBox.insert('', 'end',value=(a))           
        return result
    def ping_range(self,ip_list):
        num_threads = 5 * multiprocessing.cpu_count()
        p = multiprocessing.dummy.Pool(num_threads)
        p.map(self.ping_func, [x for x in ip_list])

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


if __name__ == '__main__':
    main()

I have tried the code using queue and after(). but still getting the same error. not have much idea on these please guide me where i am going wrong and what can i do to correct it. Thank you

import tkinter.messagebox
from tkinter import ttk
import os
import socket
import sys
import subprocess
import re
import threading
import multiprocessing.dummy
import multiprocessing
import time,  queue
    
    class Demo1:
        data=[]
        def __init__(self, master):
            self.master = master
            self.label=tkinter.Label(text="Add IP/Hostname")
            self.label.pack()
            self.t=tkinter.Text(self.master,height=20,width=50)
            self.t.pack()
            self.button = tkinter.Button(self.master,height=3,width=10, text="OK", command = self.new_window)
            self.button.pack()
            
        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.configure(bg='#6EBFE4')
            self.master.mainloop()
    class Demo2(Demo1):
        t1=[]
        s1=True
        display=[]
        
        def __init__(self, master):
            self.master=master
            self.kas(master)
        def kas(self,master):
            self.running = True
            self.queue = queue.Queue()  #queue
            Demo2.t1=Demo1.data
            self.master = master        
            cols = ('IP','Ping status')
            self.listBox = ttk.Treeview(self.master, columns=cols)
            for col in cols:
                self.listBox.heading(col, text=col)
                self.listBox.column(col,minwidth=0,width=170)
            self.listBox.column('#0',width=50)
            self.listBox.grid(row=1, column=0, columnspan=2)
            #self.ping_range(Demo2.t1)
            self.running = True
            num_threads = 5 * multiprocessing.cpu_count()
            p = multiprocessing.dummy.Pool(num_threads)
            p.map(self.ping_func, [x for x in Demo2.t1])
    
        def ping_func(self,ip):
            while self.running:
                pi=[]
                pingCmd = "ping -n 1 -w 1000 " + ip
                childStdout = os.popen(pingCmd)
                result = (childStdout.readlines())
                childStdout.close()
                pi.append(ip)
                if (any('Reply from' in i for i in result)) and (any('Destination host unreachable' not in i for i in result)):
                   pi.append("sucess")
                else:
                   pi.append("failed")
                self.queue.put(pi)  #Thread value to queue
                m = self.queue.get_nowait()
                print(m)   #getting the correct value but after this statement, getting error as main thread is not in main loop
                for i,(a) in enumerate(m):
                    self.listBox.insert('', 'end',value=(a))
                self.periodic_call()
                
        def periodic_call(self):
            self.master.after(200, self.periodic_call) #checking its contents periodically
            self.ping_func()
            if not self.running:
                import sys
                sys.exit(1)
                    
                     
    
    def main(): 
        root = tkinter.Tk()
        app = Demo1(root)
        root.mainloop()
    
    
    if __name__ == '__main__':
        main()
Jung-suk
  • 172
  • 2
  • 12
  • 1
    Tkinter is not thread safe. If all you are doing is pinging then a console application would be more appropriate. – Joshua Nixon Jul 15 '20 at 16:32
  • Hi @JoshuaNixon, Thank you for the reply. I am trying to build an application which shows the live pinging status. so i was kinda familiar with python so using tkinter. do you have any idea on how to get the 100+ ips status within 1-2 sec without using this thread concept. – Jung-suk Jul 15 '20 at 16:43
  • 2
    As @Joshua said, `tkinter` doesn't support multithreading. One way to work around that limitation is to put the results of the threads into a `Queue` and check its contents periodically using `tkinter`'s universal widget [`after()`](https://web.archive.org/web/20190222214221id_/http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/universal.html) method. My [answer](https://stackoverflow.com/questions/53696888/freezing-hanging-tkinter-gui-in-waiting-for-the-thread-to-complete) to a related question has an example of doing something like that. – martineau Jul 15 '20 at 16:47
  • Hi @martineau, Thank you for the reply. I will look into it. – Jung-suk Jul 15 '20 at 16:55
  • Hi @martineau, I have looked into your answer and tried to make it correct. As i don't have much idea on it, still getting the same error. Edited code is pasted above have a look and please guide me where i am going wrong . Thank you. – Jung-suk Jul 16 '20 at 07:07

1 Answers1

2

The reason you're still getting the RuntimeError even with the changes, is because you're is still trying to update the GUI from a thread — the one running the ping_func() method — that is not the same one running the tkinter GUI (which is a required because it doesn't support multithreading).

To fix that I have split ping_func() up into two separate parts, one the runs the ping command in another process and appends the results to the queue, and another that updates the GUI — the latter now being done in new method I added named process_incoming() (similar to the example to which I referred you).

Also note that the Demo2 class is no longer a subclass of Demo1 since there's no reason to do so (and it might confuse matters). I also changed the self.listBox attribute to self.treeview because that's what it is.

Although those changes alone would avoid the RuntimeError, in theory the GUI could still "freeze" until all the tasks completed because the pool.map() function blocks until all the tasks have completed, which could interfere with tkinter's mainloop() depending on how long that takes. To avoid that I changed pool.map() to pool.async() — which doesn't block because it's unnecessary given the Queue's contents are repeatedly polled.

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


class Demo1:
    data = []
    def __init__(self, master):
        self.master = master
        self.label=tkinter.Label(text="Add IP/Hostname")
        self.label.pack()
        self.t=tkinter.Text(self.master,height=20,width=50)
        self.t.pack()
        self.button = tkinter.Button(self.master,height=3,width=10, text="OK",
                                     command=self.new_window)
        self.button.pack()

    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.configure(bg='#6EBFE4')
        self.master.mainloop()

class Demo2:
    t1 = []
    s1 = True
    display = []

    def __init__(self, master):
        self.master = master
        self.kas(master)

    def kas(self,master):
        self.running = True
        self.queue = queue.Queue()
        Demo2.t1 = Demo1.data
        self.master = master
        cols = ('IP','Ping status')
        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.column('#0',width=50)
        self.treeview.grid(row=1, column=0, columnspan=2)

        num_threads = 5 * multiprocessing.cpu_count()
        p = multiprocessing.dummy.Pool(num_threads)
        p.map_async(self.ping_func, [x for x in Demo2.t1 if x])

        self.periodic_call()  # Start polling the queue for results.

    def ping_func(self, ip):
        pi = []
        pingCmd = "ping -n 1 -w 1000 " + ip
        with os.popen(pingCmd) as childStdout:
            result = childStdout.readlines()
        pi.append(ip)
        if(any('Reply from' in i for i in result)
           and any('Destination host unreachable' not in i for i in result)):
            pi.append("success")
        else:
            pi.append("failed")
        self.queue.put(pi)  #Thread value to queue

    def process_incoming(self):
        """ Process any messages currently in the queue. """
        while self.queue.qsize():
            try:
                msg = self.queue.get_nowait()
                print(msg)
                self.treeview.insert('', 'end', value=(msg))  # Update GUI.
            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 main():
    root = tkinter.Tk()
    app = Demo1(root)
    root.mainloop()


if __name__ == '__main__':
    main()
martineau
  • 119,623
  • 25
  • 170
  • 301
  • Hi @martineau, Thank you so much for the help. I was so confused about this. – Jung-suk Jul 16 '20 at 18:22
  • soumya: It's a complex subject, so it's easy to be confused about it. Be sure to note the additional information I just added about the potential pitfalls of using `pool.map()`. – martineau Jul 16 '20 at 18:26
  • sure sir, Thanks a lot.. It was of great help to me @martineau – Jung-suk Jul 16 '20 at 18:54