Основной цикл Tkinter, путаница после и потоков с трудоемким кодом
У меня есть рабочий код для обучения нейронной сети. Теперь я хочу создать графический интерфейс TkInter, чтобы иметь возможность, например, изменять параметры на лету. Я представляю, что это похоже на пульт дистанционного управления. Но я изо всех сил пытаюсь получить отзывчивый интерфейс. Обучение одной эпохи займет пару минут, и я не смогу ее перехватить.
Я видел много примеров, в которых метод.after(), возможно, используется для обновления часов в графическом интерфейсе, и это нормально, потому что обновление не занимает минуты. Я не могу заставить его работать, когда обратный вызов занимает несколько минут.
Я воссоздал свою проблему с минимальным количеством кода:
from time import sleep
import tkinter as tk
def trainEpoch(i):
print ("Training", i)
sleep(1) #in reality more like sleep(300)
def clickBtn():
print ("Button pressed")
root = tk.Tk()
btn = tk.Button(root, text="Click Me", command=clickBtn)
btn.grid(column=0, row=0)
for i in range (5):
trainEpoch(i)
print ("finished" )
root.mainloop()
Этот код сначала "обучает" сеть, а затем создает отзывчивое окно TkInter (которое я полностью понимаю). Чтобы быть ясным, я хочу следующего поведения: я хочу, чтобы графический интерфейс открывался до начала обучения и был доступен для кликов, пока обучение делает свое дело. Меня не волнует, разрушится ли он потом.
Нужна ли мне многопоточность для такого рода проблем?
С уважением, Лев
1 ответ
Вы не можете использовать sleep в том же потоке, что и tkinter. Поскольку tkinter является однопоточным, вы заблокируете основной цикл от выполнения каких-либо действий до завершения сна.
Вот пример использования after()
для выполнения той же задачи, не блокируя ваше приложение tkinter.
import tkinter as tk
def train_epoch(i):
if i <= 5:
print("Training", i)
i += 1
root.after(2000, lambda i=i: train_epoch(i))
else:
print("finished")
def click_btn():
print("Button pressed")
root = tk.Tk()
tk.Button(root, text="Click Me", command=click_btn).grid(column=0, row=0)
train_epoch(1)
root.mainloop()
Полученные результаты:
Если вам нужно использовать потоки, вы можете взаимодействовать с переменной в глобальном пространстве имен между tkinter и потоковой функцией.
См. Этот пример:
import tkinter as tk
import threading
import time
def train_epoch():
global some_var
while some_var <= 5:
print(some_var)
time.sleep(0.5)
def click_btn():
global some_var
some_var += 1
print("Button pressed")
root = tk.Tk()
some_var = 0
tk.Button(root, text="Click Me", command=click_btn).pack()
thread = threading.Thread(target=train_epoch)
thread.start()
root.mainloop()