0

I currently have a very extensive python project (>8000 lines) that uses Threads to execute several operations. It also uses tkinter to make a GUI application, and before you point that out, I'm very aware that tkinter and threading don't mix very well, but I hope that's not what is causing my trouble here.

from threading import Thread

The root of my problem is that even after closing my Tkinter Application, it still runs on task manager, never completely closing. I believe this is caused by Threads still active and waiting to update widgets within the code, even after the main root is destroyed.

Since the project is so large, before I go make a python list containing all the thread objects to then call .close on each of them upon the closure of the main root, is there any command within the threading library that can in one line close all of the current active Threads?

Also, since I code as a hobby and I'm still very new to python, am I doing something very wrong or using incorrect nomenclature to communicate? lol

Thank you very much!

Also my version of everything (python+libraries) is the latest.

--What I'm about to do and what I expect--

I'm about to create a global list and then put all thread objects within it to then call .close on each of them.

I will probably change the "WM_DELETE_WINDOW" protocol to run a function that does "for obj in thread_names_list, obj.close()", before destroying the root.

I expect this to solve the task manager problem. Hopefully...

======Update======

An example of how I call threads

from threading import Thread
import tkinter as tk
import time
def main():
    global root, button_foo
    root = tk.Tk() 
    root.state('zoomed')
    button_foo = tk.Button(root, text='Hello')
    button_foo.place(x= 200, y= 200) 
    Thread(target=updates_button).start()
    root.mainloop()
 
def updates_button():
    time.sleep(10)
    button_foo.config(text='Time is passing...')

if __name__ == '__main__':
    main()  
  • Python threads do not have a close() function – DarkKnight Aug 19 '23 at 18:29
  • 1
    If you make them `daemon` threads, all daemon threads are abruptly killed when the application exits. You're aware that threads cannot update UI objects, right? Are you using `callafter` to do the updates? – Tim Roberts Aug 19 '23 at 18:29
  • @TimRoberts I'm targetting a function with threading.Thread, and that function updates widgets via .config or .itemconfig. There are often time.sleeps within those threaded functions. I'm unfamiliar with `callafter` – ChocolaToni Aug 19 '23 at 18:53
  • I was also unfamiliar with the `daemon` threads until now, and now that I'm searching about them, I find the concept very interesting! For sure I'm looking deep into those, so thank you for mentioning them! – ChocolaToni Aug 19 '23 at 18:56
  • Updated the question to include a minimal example of how I use threads within my code. Perhaps it's also important to add that most of my threads are used to update Canvas Image objects. Some of those threads end by looping, calling themselves, so the animations loop. – ChocolaToni Aug 19 '23 at 19:09
  • What you are doing is NOT VALID. Maybe it works sometimes, on some platforms, but for example it won't ever work on MacOS. You CANNOT touch any GUI components from a non-main thread. That's a rule that you must embed in your brain. What you have above is the reason the `after` method was invenited. Do `button_foo.after( 10000, updates)`. – Tim Roberts Aug 19 '23 at 22:33
  • Thank you so much for warning me, as well as anyone who comes to this question in the future, about this, @TimRoberts! I currently code on Windows, but I do intend the project to cross platform later on, so your insights will surely save me a lot of time and effort in the future. From now on, I will inform myself more regarding this topic, and I'm glad I came to ask this question! – ChocolaToni Aug 21 '23 at 02:52

1 Answers1

0

There is already a SO question about how to kill a thread. There are many methods to do so. Setting Thread( ..., daemon=True) might be enough but is generally considered a bad pattern. If refactoring the 8000+ lines of code in your project is viable, it would be better to use

concurrent.futures.ThreadPoolExecutor, a higher level interface to push tasks to a background thread

from concurrent.futures import ThreadPoolExecutor
import tkinter as tk
import time

def main():
    global root, button_foo
    root = tk.Tk() 
    root.state('zoomed')
    # ...
    
    # create thread pool and let the context manager to shut it down
    with ThreadPoolExecutor(max_workers=2) as executor: 
        # submit updates_button to a background thread
        executor.submit(updates_button) 
        # main thread continues to run the mainloop
        root.mainloop()

 
# ...

This uses the context manager to automatically create and shutdown the thread pool. You can also manage it manually for better controlling how the threads stop. Call executor.shutdown with options like wait and cancel_futures.

The update_button function also needs refactoring because you should not update the GUI outside the main thread. See this and this SO answers for proper ways to update the Tk widget.

gdlmx
  • 6,479
  • 1
  • 21
  • 39
  • *it is generally considered a bad pattern* -- By who? Can you cite any references? – Tim Roberts Aug 19 '23 at 22:34
  • @TimRoberts By the author of the [SO answer](https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread) that I cited. "It is generally a bad pattern to kill a thread abruptly", and according to the [official doc](https://docs.python.org/3/library/threading.html#thread-objects), "daemon threads are abruptly stopped at shutdown". – gdlmx Aug 20 '23 at 19:40
  • Yes, BUT in this case the application is known to be dying. Terminating abruptly seems the appropriate action. We don't care about work that was left undone. – Tim Roberts Aug 20 '23 at 22:51