- threading
- queue
- ctypes
Tk: Runner with threads
examples/tk/tk_runner_threads.py
import tkinter as tk import time import threading import queue import ctypes class MyStopButton(Exception): pass class ThreadedJob(threading.Thread): def __init__(self, que): self.que = que threading.Thread.__init__(self) def run(self): thread = threading.current_thread() print("Start thread {}".format(thread.name)) try: for i in range(10): print(i) self.que.put(str(i)) time.sleep(1) except Exception as err: print(f"Exception in {thread.name}: {err} {err.__class__.__name__}") def raise_exception(self): thread = threading.current_thread() print(f"Raise exception in {thread.name}") thread_id = self.native_id res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(MyStopButton)) if res > 1: ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0) print('Exception raise failure') print("DONE") class RunnerApp(tk.Frame): def __init__(self, parent=None): super().__init__(parent) self.pack() # Capture event when someone closes the window with the X on the top-right corner of the window parent.protocol("WM_DELETE_WINDOW", self.close_app) self.QUIT = tk.Button(self) self.QUIT["text"] = "QUIT" self.QUIT["fg"] = "red" self.QUIT["command"] = self.close_app self.QUIT.pack({"side": "left"}) self.start_button = tk.Button(self) self.start_button["text"] = "Start" self.start_button["command"] = self.start self.start_button.pack({"side": "left"}) self.stop_button = tk.Button(self) self.stop_button["text"] = "Stop" self.stop_button["command"] = self.stop self.stop_button.pack({"side": "left"}) self.text = tk.Text(self, state='disabled') self.text.pack({"side": "bottom"}) self.stop_process = False def close_app(self): print("close") self.stop_process = True self.quit() def stop(self): print("stop") print(self.job.name) self.job.raise_exception() #self.stop_process = True self.add_line('stop') def start(self): self.stop_process = False self.start_button['state'] = 'disabled' self.que = queue.Queue() self.job = ThreadedJob(self.que) self.job.start() self.master.after(100, self.process_queue) def process_queue(self): print("process " + str(time.time())) if not self.job.is_alive(): self.job.join() self.job = None self.stop_process = True self.start_button['state'] = 'normal' print("finished") return try: msg = self.que.get(0) self.add_line(msg) except queue.Empty: pass finally: if not self.stop_process: self.master.after(100, self.process_queue) def add_line(self, line): self.text['state'] = 'normal' # allow editing of the Text widget self.text.insert('end', line + "\n") self.text['state'] = 'disabled' # disable editing self.text.see('end') # scroll to the end as we make progress self.update() # update the content and allow other events (e.g. from stop and quit buttons) to take place def main(): tk_root = tk.Tk() app = RunnerApp(parent=tk_root) tk_root.lift() tk_root.call('wm', 'attributes', '.', '-topmost', True) tk_root.after_idle(tk_root.call, 'wm', 'attributes', '.', '-topmost', False) app.mainloop() main()