Основной цикл 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()
Другие вопросы по тегам