'RuntimeError: main thread is not in main loop when tkinter GUI is closed

I'm trying to create a tkinter GUI that presents the 10 processes that are taking up the cpu, refreshing it every 2 seconds, similar to top, on the linux command line. My code is:

    import time
    import threading
    import logging
    import psutil
    from functools import cmp_to_key
    from tkinter import *
    import tkinter.scrolledtext as ScrolledText

    class TextHandler(logging.Handler):

        def __init__(self, text):
            logging.Handler.__init__(self)
            self.text = text

        def emit(self, record):
            msg = self.format(record)
            def append():
                self.text.configure(state='normal')
                self.text.insert(END, msg + '\n')
                self.text.configure(state='disabled')
                # Autoscroll to the bottom
                self.text.yview(END)
            # This is necessary because we can't modify the Text from other threads
            self.text.after(0, append)

    class myGUI(Frame):

        def __init__(self):
            self.window = Tk()
            self.window.title("TOP GUI")
            self.window.configure(bg='#999999')
            self.window.minsize(width=400, height=400)
            self.window.resizable(0, 0)

            self.lable_title = Label(self.window,
                                     text=" Table of Processes ",
                                     font=("Arial", 20, "bold"),
                                     bg='#4F4F4F', fg='#E6BA00', highlightthickness=2)
            self.lable_title.pack(pady=10)

            self.frame_label1 = Label(self.window,
                                      text='\n Below are listed the 10 processes with the highest \n'
                                           + 'CPU consumption, with 5 second refresh. \n',
                                      bg='#E6BA00',
                                      highlightthickness=0,
                                      width=43,
                                      borderwidth=2,
                                      relief='ridge')
            self.frame_label1.pack(pady=20)

            self.frame_top_message = Frame(self.window, bg='#999999')
            self.frame_botao_go = Frame(self.window, bg='#999999')

            self.frame_top_message.pack(pady=20)
            self.frame_botao_go.pack(pady=20)

            self.button_completed = Button(self.frame_botao_go,
                                           font='Arial 14 bold',
                                           text='GO',
                                           width=10,
                                           command=self.window.destroy)
            self.button_completed.pack(side='left', padx=50)
            self.button_completed.configure(bg='#E6BA00')

            # Add text widget to display logging info
            st = ScrolledText.ScrolledText(self.frame_top_message)
            st.configure(font='TkFixedFont',
                         width=50,
                         height=15)
            st.pack(padx=30)

            # Create textLogger
            text_handler = TextHandler(st)

            # Logging configuration
            logging.basicConfig(filename='test.log',
                                level=logging.INFO,
                                format='%(asctime)s - %(levelname)s - %(message)s')

            # Add the handler to logger
            logger = logging.getLogger()
            logger.addHandler(text_handler)
            trheading()

            self.window.mainloop()

    def trheading():
        t2 = threading.Thread(target=worker, args=[])
        t2.start()

    def worker():
        def cmpCpu(a, b):
            a = a['cpu']
            b = b['cpu']
            if a > b:
                return -1
            elif a == b:
                return 0
            else:
                return 1

        while True:
            timestamp = time.asctime()
            logging.info(timestamp)

            # Collect information for each process
            processes = []
            for proc in psutil.process_iter(attrs=['name', 'cpu_percent']):
                processes.append({'name': proc.info['name'], 'cpu': proc.info['cpu_percent']})

            # Sort by cpu usage
            logging.info("CPU:")
            processes.sort(key=cmp_to_key(cmpCpu))
            for i in range(10):
                info = processes[i]
                info = info['name'] + ", " + str(info['cpu']) + "%"
                logging.info(info)
            logging.info("\n")
            time.sleep(2)

    t1 = threading.Thread(target=myGUI, args=[])
    t1.start()

It will be used as a module of another larger program and will allow the user to follow a series of processes launched through subprocess.run. It was written by putting together several examples from the internet. It works very well, but when I click the GO button I get the error message:

    Exception in thread Thread-2:
    Traceback (most recent call last):
      File "/home/arymaia/.pyenv/versions/3.9-dev/lib/python3.9/threading.py", line 973, in _bootstrap_inner
        self.run()
      File "/home/arymaia/.pyenv/versions/3.9-dev/lib/python3.9/threading.py", line 910, in run
        self._target(*self._args, **self._kwargs)
      File "/home/arymaia/PycharmProjects/TopIso3D_Viewer_vbeta/Table_of_Processes_2.py", line 116, in worker
        logging.info(timestamp)
      File "/home/arymaia/.pyenv/versions/3.9-dev/lib/python3.9/logging/__init__.py", line 2097, in info
        root.info(msg, *args, **kwargs)
      File "/home/arymaia/.pyenv/versions/3.9-dev/lib/python3.9/logging/__init__.py", line 1446, in info
        self._log(INFO, msg, args, **kwargs)
      File "/home/arymaia/.pyenv/versions/3.9-dev/lib/python3.9/logging/__init__.py", line 1589, in _log
        self.handle(record)
      File "/home/arymaia/.pyenv/versions/3.9-dev/lib/python3.9/logging/__init__.py", line 1599, in handle
        self.callHandlers(record)
      File "/home/arymaia/.pyenv/versions/3.9-dev/lib/python3.9/logging/__init__.py", line 1661, in callHandlers
        hdlr.handle(record)
      File "/home/arymaia/.pyenv/versions/3.9-dev/lib/python3.9/logging/__init__.py", line 952, in handle
        self.emit(record)
      File "/home/arymaia/PycharmProjects/TopIso3D_Viewer_vbeta/Table_of_Processes_2.py", line 27, in emit
        self.text.after(0, append)
      File "/home/arymaia/.pyenv/versions/3.9-dev/lib/python3.9/tkinter/__init__.py", line 821, in after
        name = self._register(callit)
      File "/home/arymaia/.pyenv/versions/3.9-dev/lib/python3.9/tkinter/__init__.py", line 1528, in _register
        self.tk.createcommand(name, f)
    RuntimeError: main thread is not in main loop
    Tcl_AsyncDelete: async handler deleted by the wrong thread

I researched several answers that explain that tkinter has limitations with the use of trheading, but what I found different was that the code I made works without error, until the moment of being closed. Could someone explain to me why this is and how I can solve this problem?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source