@ArtiomK

Возможно ли запустить функцию в одном потоке и заставить Tkinter Progressbar двигаться в другом потоке mainloop, пока эта функция выполняется?

У меня есть функция, которая читает файл, затем обрабатывает данные и пишет их в другой файл, я хочу, чтобы Tkinter.ttk progressbar "бегала" пока идет выполнение этой функции. Вместо ожидаемого результата я получаю, что пользовательский интерфейс зависает пока не выполнится функция в другой thread, после того, как функция завершается пользовательский интерфейс отвисает и progressbar начинает бежать. Возможно это сделать в Tkinter, наткнулся на информацию, что он не умеет работать с multithreading

Небольшой кусок кода:
# Ранее я нажал кнопку на запуск функции
self.prbar.start(10)  # progressbar должен начать работать
x = threading.Thread(target=do_work, args=(filename, text)) # запускаю в другом потоке функцию
x.start() # start thread
x.join() # функция завершена
# А вот теперь progressbar только начинает свой бег
messagebox.showinfo("Info", message="Work is done")
  • Вопрос задан
  • 1804 просмотра
Решения вопроса 1
sergey-gornostaev
@sergey-gornostaev Куратор тега Python
Седой и строгий
События графического интерфейса обрабатываются бесконечным циклом, который запускается, когда вы вызываете 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()
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
tsarevfs
@tsarevfs
C++ developer
Поскольку tk не потокобезопасный изменять показания прогрессбара придется из главного потока. Заведите переменную для хранения прогресса. Тред воркер будет писать в нее актуальное состояние а главный тред читать и применять новое значение к прогрессбару по таймеру. Не забудте про Lock при чтении и записи.
Как упомянул Сергей Горностаев, использовать join не вариант. Вероятно вам подойдет daemon тред.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы