@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")
  • Вопрос задан
  • 849 просмотров
Решения вопроса 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 тред.
Ответ написан
Ваш ответ на вопрос

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

Войти через центр авторизации
Похожие вопросы
от 80 000 до 200 000 ₽
2Reallife Москва
от 150 000 до 170 000 ₽
Improvado Новосибирск
от 140 000 до 200 000 ₽
26 нояб. 2020, в 00:27
500 руб./в час
25 нояб. 2020, в 23:13
1000 руб./за проект
25 нояб. 2020, в 22:43
800 руб./в час