Question: I'm looking for a way to track a multiprocessing task with Tkinter progress bar. This is something that can be done very straightforwardly with tqdm for display in the terminal. Instead of using tqdm I'd like to use ttk.Progressbar, but all attempts I have made at this, the tasks block on trying to update the progressbar (e.g. using update_idletasks and similar). Below is a template of the kind of solution I'm looking for: import time from multiprocessing import Pool from tqdm import tqdm import tkinter as tk import tkinter.ttk as ttk def task(x): time.sleep(0.1) return x * x def start_task(): num_processes = 12 num_tasks = 100 with Pool(processes=num_processes) as pool: with tqdm(total=num_tasks, desc="Processing") as pbar: def update_progress(_): # <Insert update to tk progress bar here> pbar.update(1) for i in range(num_tasks): pool.apply_async(task, args=(i,), callback=update_progress) pool.close() pool.join() if __name__ == "__main__": root = tk.Tk() root.title("Task Progress") progress_bar = ttk.Progressbar(root, maximum=100, length=300) progress_bar.pack(pady=20) button = tk.Button(text="Start", command=start_task) button.pack(fill="x", padx=10, pady=10) root.mainloop()  In the solution I'd also like to get the output of the task (in this case a list of x*x). If another multiprocessing structure would work better please feel free to adjust (pool just seemed the simplest for demonstration). This is a question that has been asked on Stack Overflow before, but all the previous answers I've found have not been minimal examples and I didn't find them very helpful.

Answer: Calling pool.join() blocks the main thread until all the tasks are done, which causes Tkinter to hang. To get around this, you can call start_task in a thread. Running the thread with .start() (instead of .join()) will make it run in the background so it doesn't block the main thread. You can pass lists to the thread for keeping track of progress and results. These lists can be updated by the update_progress function, which takes the return value of task as an argument. To update the progress bar I've added the update_bar function. This removes each value from progress using pop, then increases the progress bar by that amount with step. This is run every 100ms while the thread is alive. Once it finishes, use_results is called, which can then do something with the result (I've made it display the sum of values as an example). Finally, I've introduced start_start_task (which could probably be named better but you get the idea). This is called when the button is clicked and initialises the lists, starts the thread and calls the update_bar function. import time from multiprocessing import Pool from threading import Thread import tkinter as tk import tkinter.ttk as ttk def task(x): time.sleep(0.1) return x*x def start_task(progress, results): num_processes = 12 num_tasks = 100 with Pool(processes=num_processes) as pool: def update_progress(result): progress.append(1) results.append(result) for i in range(num_tasks): pool.apply_async(task, args=(i,), callback=update_progress) pool.close() pool.join() def update_bar(thread, progress, results): # check for progress while progress: progress_bar.step(progress.pop()) # if the tasks are still running then call the update function after 100ms if thread.is_alive(): root.after(100, lambda: update_bar(thread, progress, results)) # if the tasks are done call the result handler else: use_results(results) def use_results(results): # do whatever you want with the result result_label.config(text = f"Sum of results: {sum(results)}") def start_start_task(): progress = [] results = [] thread = Thread(target=start_task, args=(progress, results)) thread.start() update_bar(thread, progress, results) if __name__ == "__main__": root = tk.Tk() root.title("Task Progress") progress_bar = ttk.Progressbar(root, maximum=100, length=300) progress_bar.pack(pady=20) button = tk.Button(root, text="Start", command=start_start_task) button.pack(fill="x", padx=10, pady=10) result_label = tk.Label(root, text="Press Start to get result") result_label.pack() root.mainloop()