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

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

Войти через центр авторизации
Похожие вопросы
10 мая 2024, в 11:47
500 руб./за проект
10 мая 2024, в 11:36
30000 руб./за проект
10 мая 2024, в 11:27
1000 руб./за проект