RuntimeError: main thread is not in main loop
def work():
result = slow_thing()
label.config(text=result)
threading.Thread(target=work).start()
The thread updated a widget directly. Tk only accepts calls from the thread that created it, and the error sometimes appears much later than the offending line, which makes it confusing to track down.
import threading
def work():
result = slow_thing()
root.after(0, lambda: label.config(text=result))
threading.Thread(target=work, daemon=True).start()
after(0, ...) puts the call on Tk's own queue, so it runs on the main thread at the next opportunity. Every widget update from a thread has to go through it.
import queue, threading
results = queue.Queue()
def work():
for n in range(100):
results.put(n)
def drain():
while not results.empty():
bar["value"] = results.get()
root.after(100, drain)
threading.Thread(target=work, daemon=True).start()
drain()
The thread only ever puts things in the queue. The main loop checks the queue on a timer. Nothing in the thread touches a widget.
Often you can. If the slow work splits into pieces, schedule them with after instead and skip threading entirely. See events and bindings.
The PIL to Pillow converter finds most of these automatically, and the other error pages cover the rest.