pythonmultithreadingthreadpoolpython-multithreading

Cannot start a ThreadPoolExecutor inside a threading.Thread function


In this example, Tkinter GUI starts ThreadPoolExecutor. But ThreadPoolExecutor is inside a threading.Thread function. The thread function says it's finished before ThreadPoolExecutor has started ... and returns this error.

RuntimeError: can't register atexit after shutdown

Here is the code:

import time
import threading
import tkinter as tk
import concurrent.futures
import random


def thread1(_details, lock):
    print('started ' + _details[2])
    time.sleep(random.randint(1, 10))
    print('finished ' + _details[2])
    return _details


def workpool():
    file_lock = threading.Lock()
    list1 = [['0', 'pending', 'name: test1'], ['1', 'pending', 'name: test2'], ['2', 'pending', 'name: test3'], ['4', 'pending', 'name: test4'], ['7', 'pending', 'name: test5'], ['8', 'pending', 'name: test6']]

    print('thread running')
    with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
        working_threads = {executor.submit(thread1, _details, file_lock): _details for index1, _details in enumerate(list1)}

        for future in concurrent.futures.as_completed(working_threads):
            current_result = future.result()
    print('threads done')


def launch_threads():
    workpool()

def new_button():
    threading.Thread(name='Launch_ThreadPoolExecutor', target=launch_threads).start()


def main_thread():
    root = tk.Tk()
    root.geometry("+{}+{}".format(650, 50))
    root.geometry("{}x{}".format(200, 200))
    new_bt = tk.Button(root, text="New", command=new_button, height=2, padx=10, pady=5, width=15, wraplength=100)
    new_bt.place(x=40, y=40, height=30, width=80)
    root.mainloop()


threading.Thread(name='GUI_thread', target=main_thread).start()

Here is the full traceback:

Exception in thread Launch_ThreadPoolExecutor:
Traceback (most recent call last):
  File "C:\Users\User\AppData\Local\Programs\Python\Python313\Lib\threading.py", line 1041, in _bootstrap_inner
    self.run()
    ~~~~~~~~^^
  File "C:\Users\User\AppData\Local\Programs\Python\Python313\Lib\threading.py", line 992, in run
    self._target(*self._args, **self._kwargs)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\GOOD\Coding\.Coding_Projects\test3.py", line 24, in workpool
    with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\User\AppData\Local\Programs\Python\Python313\Lib\concurrent\futures\__init__.py", line 50, in __getattr__
    from .thread import ThreadPoolExecutor as te
  File "C:\Users\User\AppData\Local\Programs\Python\Python313\Lib\concurrent\futures\thread.py", line 37, in <module>
    threading._register_atexit(_python_exit)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  File "C:\Users\User\AppData\Local\Programs\Python\Python313\Lib\threading.py", line 1503, in _register_atexit
    raise RuntimeError("can't register atexit after shutdown")
RuntimeError: can't register atexit after shutdown

Solution

  • Tested only on Linux.


    Code works correctly for me when I use .join() for first thread GUI_thread

    p = threading.Thread(name="GUI_thread", target=main_thread)
    p.start()
    p.join()
    

    Code works correctly also if I run directly

    main_thread()
    

    I don't think it needs to keep all GUI in thread.
    Only code executed by button may need to use thread because only this code may freeze GUI.


    Full working code

    import time
    import threading
    import tkinter as tk
    import concurrent.futures
    import random
    
    
    def thread1(_details, lock):
        print("started " + _details[2])
        time.sleep(random.randint(1, 10))
        print("finished " + _details[2])
        return _details
    
    
    def workpool():
        file_lock = threading.Lock()
        list1 = [
            ["0", "pending", "name: test1"],
            ["1", "pending", "name: test2"],
            ["2", "pending", "name: test3"],
            ["4", "pending", "name: test4"],
            ["7", "pending", "name: test5"],
            ["8", "pending", "name: test6"],
        ]
    
        print("thread running")
        with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
            working_threads = {
                executor.submit(thread1, _details, file_lock): _details
                for index1, _details in enumerate(list1)
            }
    
            for future in concurrent.futures.as_completed(working_threads):
                current_result = future.result()
        print("threads done")
    
    
    def launch_threads():
        workpool()
    
    
    def new_button():
        threading.Thread(name="Launch_ThreadPoolExecutor", target=launch_threads).start()
    
    
    def main_thread():
        root = tk.Tk()
        root.geometry("+{}+{}".format(650, 50))
        root.geometry("{}x{}".format(200, 200))
        new_bt = tk.Button(root, text="New", command=new_button, height=2, padx=10, pady=5, width=15, wraplength=100)
        new_bt.place(x=40, y=40, height=30, width=80)
        root.mainloop()
    
    # --- only changes are here ---
    
    # version 1 - works correctly on Linux
    p = threading.Thread(name="GUI_thread", target=main_thread)
    p.start()
    p.join()
    
    # version 2 - works correctly on Linux
    #main_thread()