Как мне передать флаг на остановку из GUI в Thread с циклом без global variables?
В продолжение темы о том, что global variables - зло. (Вот тут в комментариях)
Допустим, я согласен с этим и не хочу их использовать в своей программе.
У меня есть GUI, из которого я осуществляю общее управление. При нажатии кнопки Старт в GUI у меня запускается Thread, в котором у меня стоит цикл, который для меня выполняет работу. Когда я нажимаю кнопку Стоп в GUI, мне нужно чтобы у меня цикл выполнил текущий проход, выполнил несколько действий и закончился.
Так же у меня идет передача информации о текущей активности в обратном направлении (в частности имя рабочего файла цикла пишется в Label в gui)
Сейчас я это реализовал через глобальную переменную-boolean, и проверку этой переменной в конце очередного прохода цикла.
Как эту передачу флага на остановку, или возврат имени рабочего файла, осуществить правильно? Не прибить программу, не прибить thread, а именно сделать так, чтобы цикл в определенном месте выполнит определенные действия и завершится?
Кусочки кода:
# В инициализации gui я задаю:
global active_file_path
active_file_path = StringVar()
active_file_path.set("No active file now")
active_file_way = ttk.Label(textvariable=active_file_path, anchor="center")
active_file_way.grid(row=1, column=0, columnspan=3, sticky=E + W + N)
global should_stop
should_stop = BooleanVar()
should_stop.set(False)
# В Thread'е я делаю каждый проход цикла:
active_file_path.set(file_name)
# И в конце прохода:
if should_stop.get():
...
# Кнопка Стоп делает:
should_stop.set(True)
Update 1 (к комментариям):
Я задаю в описании gui текст через textvariable и позже этот textvariable меняю при необходимости:
class main_app(tk.Frame):
def __init__(self, parent, *args, **kwargs):
self.shared_state = SharedState()
active_file_way = ttk.Label(textvariable=self.shared_state.active_file_path, anchor="center")
# В классе с флагом я задаю вот так:
class SharedState:
def __init__(self):
self.active_file_path = tk.StringVar()
self.active_file_path.set("No active file now")
# И потом в цикле у меня стоит:
for file in os.listdir(directory):
shared_state.active_file_path.set(file_address)
При таком использовании при вызове метода .set() текст в Label сразу изменяется.
Оно работает. Но нету ли с ним такого же расклада, как с работающими global'ами?
Почему спрашиваю - там где я это нашел, именно эти StringVar() и объявлялись как global, вот потому и спрашиваю.
Ответы (1 шт):
Создайте (не глобальный) объект, через который будет происходить взаимодействие.
При создании потока передавайте ссылку на этот объект в функцию потока, тогда она сможет вызывать у этого объекта методы и изменять атрибуты. Условный код:
class SharedState:
def __init__(self):
self.finished = False
def background_job(shared_state):
while not shared_state.finished:
# работаем в цикле
class SomeUIComponent:
def some_ui_function(self):
self.shared_state = SharedState()
thread = Thread(target=background_job, args(self.shared_state,))
thread.start()
def some_other_ui_function(self):
self.shared_state.finished = True
Тот код, который создает поток, т.е. UI, тоже имеет доступ к этому объекту, ведь он его создал. Значить UI может читать из этого объекта значения атрибутов. То есть UI и фоновый поток могут между собой взаимодействовать через разделяемый объект, на который у них есть ссылки.
Важно только синхронизировать доступ к этому объекту, если важна атомарность операций изменения. Простой способ для передачи более сложных данных чем один флаг - это queue.
Что касается использования tk.StringVar в SharedState, то тут нет никакого использования глобальных переменных. Если класс tk.StringVar потокобезопасный, то никаких проблем. Но я бы не вносил код, который знает что-то про UI (а tk.StringVar таким является) в SharedState. Лучше чтоб там были просто строки.