Tkinter Python Crashes On New Thread Trying To Log On Main Thread
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"