Search code examples
pythontkinterpython-multithreadingping

Main thread is not in main loop


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

Solution

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