Skip to content Skip to sidebar Skip to footer

Tkinter Python Crashes On New Thread Trying To Log On Main Thread

i have created a tkinter gui that does some time consuming operations when clicking on some buttons. There are 2 main problems in my case that i think cause tkinter to crash in an

Solution 1:

As I said in comments - your error occures because you passing your text widget alongside with instance of TextHandler to a separate thread. And some methods of TextHandler casts methods of your text widget (e.g. emit), whom need to be casted from main thread.

The simpliest solution to overcome your problem is a queue object. You can't cast tkinter methods (e.g. write something to a widget) from other threads, but you can populate a queue! The main idea here - continiously check a queue while anyone of a task's threads exists. To continiously check the queue we need an after "loop" and list of tasks:

deflisten(self, force_start=False):
    #   "after" loop - listener
    self.listen_queue()

    if self.task_list or force_start:
        print('Listener: Listen')
        self.after(100, self.listen)
    else:
        print('Listener: Off')

Inside whom we trying to pull something from a queue:

deflisten_queue(self):
    #   listen queuewhile self.log_queue.qsize():
        try:
            self.logger.warning(self.log_queue.get())
        except queue.Empty:
            pass

Of course you can start to listen a queue right from the start of your application and, in general, your loop would look like this:

deflisten(self, force_start=False):
    #   "after" loop - listener
    self.listen_queue()
    self.after(100, self.listen)

But again, if you want more control over threads - list with them is a good idea! I think that now you got the pattern, so here's a complete example:

#   importstry:
    import tkinter as tk              # Python 3import queue
except ImportError:
    import Tkinter as tk              # Python 2import Queue as queue

import logging
import threading
import random
import string


#   classesclassTextHandler(logging.Handler):
    def__init__(self, text):
        # run the regular Handler __init__
        logging.Handler.__init__(self)
        # Store a reference to the Text it will log to
        self.text = text

    defemit(self, record):
        msg = self.format(record)

        self.text.configure(state='normal')
        self.text.insert(tk.END, msg + '\n')
        self.text.configure(state=tk.DISABLED)
        self.text.yview(tk.END)


classApp(tk.Tk):
    #   common tk appdef__init__(self):
        #   initiate root
        tk.Tk.__init__(self)
        self.resizable(width=False, height=False)

        #   initiate widgets
        self.spawn_task_button = tk.Button(self, text='Spawn Task', command=self.spawn_task)
        self.spawn_task_button.pack(expand=True, fill='x')

        self.quit_button = tk.Button(self, text='Quit', command=self.close_app)
        self.quit_button.pack(expand=True, fill='x')

        self.text_console = tk.Text(self, bg='black', fg='white')
        self.text_console.pack(expand=True, fill='both')

        #   initiate queue, task list, logger
        self.logger = logging.getLogger()
        self.logger.addHandler(TextHandler(self.text_console))

        self.log_queue = queue.Queue()
        self.task_list = []

        #   initiate events and protocols
        self.protocol('WM_DELETE_WINDOW', self.close_app)

    defput_line_to_queue(self, log_line=''):
        #   put log line to queue
        self.log_queue.put(log_line)

    deflisten_queue(self):
        #   listen queuewhile self.log_queue.qsize():
            try:
                self.logger.warning(self.log_queue.get())
            except queue.Empty:
                passdeflisten(self, force_start=False):
        #   "after" loop - listener
        self.listen_queue()

        if self.task_list or force_start:
            print('Listener: Listen')
            self.after(100, self.listen)
        else:
            print('Listener: Off')

    defcommon_task(self, task_thread):
        #   example task wait + print#   add task to task_list
        self.task_list.append(task_thread)

        iteration_count = random.randint(1, 10)
        task_numb = task_thread.name[-1:]

        self.put_line_to_queue('\n*** Task %s: Spawned \t Iteration count: %d ***\n' % (task_numb, iteration_count))

        for _ inrange(iteration_count):
            threading.Event().wait(1)
            self.put_line_to_queue('Task %s: In Progress \t Iteration: %d \t Generated: %s' % (task_numb, _ + 1,
                                                                                                 generate_smth()))

        self.put_line_to_queue('Task %s: Completed\n' % task_numb)

        #   remove task from task_list
        self.task_list.remove(task_thread)

    defspawn_task(self):
        #   spawn another task
        task = threading.Thread(target=lambda: self.common_task(task))

        #   "kick start" listener if task list is emptyifnot self.task_list:
            self.listen(force_start=True)

        task.start()

    defclose_app(self):
        # handle closingif self.task_list:
            #   code to handle threads#   there're some threads in a list
            self.put_line_to_queue('\n**** Cant quit right now! ****\n')
        else:
            self.destroy()


#   functionsdefgenerate_smth(size=6, chars=string.ascii_uppercase + string.digits):
    # generate randomreturn''.join(random.choice(chars) for _ inrange(size))

#   entry point
app = App()
app.mainloop()

Post a Comment for "Tkinter Python Crashes On New Thread Trying To Log On Main Thread"