События графического интерфейса обрабатываются бесконечным циклом, который запускается, когда вы вызываете
root.mainloop()
Его нельзя останавливать, иначе приложение зависнет. А блокирующий вызов
x.join()
как раз это и делает.
Правильный способ - это обмен событиями между потоками и передача данных через очередь:
from functools import partial
import threading
import time
import tkinter as tk
from tkinter.ttk import Progressbar
import queue
def worker(q, r):
for i in range(100):
# Передаём в очередь текущее значение
q.put(i + 1)
# Генерируем событие
r.event_generate('<<Updated>>', when='tail')
# Спим для наглядности
time.sleep(0.1)
def on_update(event, q=None, pb=None):
# Получаем данные из очереди
pb['value'] = q.get()
# Создаём очередь для обмена данными между поткоами
q = queue.Queue()
# Создаём окно
root = tk.Tk()
progressbar = Progressbar(root, orient=tk.HORIZONTAL, length=100, mode='determinate')
progressbar.pack()
# "Передаём" в обработчик ссылки на очередь и progressbar
handler = partial(on_update, q=q, pb=progressbar)
# Регистрируем обработчик для события обновления progressbar'а
root.bind('<<Updated>>', handler)
# Создаём поток и передаём в него ссылки на очередь и окно
t = threading.Thread(target=worker, args=(q, root))
t.start()
root.mainloop()