Всем привет! для обновления окна использую
root.update()
Но если программа делает например запрос к серверу и долго не получает ответ- программа подвисает на время ожидания ответа. Как этого избежать и сделать так, чтобы программа была всегда активна? Спасибо!
пример кода:
root=tk.Tk()
def work ():
requests.get(url)
time.sleep(5)
work()
root.mainloop()
Ответ
Никогда никогда не используйте time.sleep(5) внутри цикла событий -- это замораживает ваше GUI на ~5 секунд. Используйте root.after(5000, some_action), чтобы выполнить код через 5 секунд в Tkinter.
Чтобы выполнить работу, которая требует не фиксированное время (такую как отправка/чтение данных из сети, используя requests.get()), следует либо вынести в фоновый поток (threading), либо встроить операции ввода/вывода в сам цикл событий (Widget.tk.createfilehandler()). Примеры кода по ссылкам можно посмотреть: Мультизадачность на Python: выполнить две долгие функции одновременно, не блокируя GUI Конкретно, вот пример для tkinter, который читает вывод из внешней команды (self.proc.stdout.readline()), не блокируя цикл событий:
вариант с фоновым потоком
вариант без потока с createfilehandler()
Вот полный пример кода, который запускает requests.get() в фоновом потоке и обновляет показ текущего времени каждые 100 миллисекунд, пока запрос не завершится:
#!/usr/bin/env python3
import time
import tkinter
from datetime import datetime
from queue import Empty, Queue
from threading import Thread
import requests
def display_result(label, q):
try:
label['text'] = q.get(block=False)
except Empty: # update time at 100ms boundary
label.after(round(100 - (1000 * time.time()) % 100),
display_result, label, q)
label['text'] = str(datetime.now().strftime("%H:%M:%S.%f")[:-3])
def get_result(q):
# blocking function
q.put('ip=' + requests.get('https://httpbin.org/delay/3').json()['origin'])
# get result in a background thread
result_queue = Queue()
Thread(target=get_result, args=[result_queue], daemon=True).start()
# display result
root = tkinter.Tk()
label = tkinter.Label(font=(None, 100))
label.pack()
display_result(label, result_queue)
# center window
root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id()))
root.mainloop()
Чтобы обновить надпись (tkinter.Label) из другого потока, самый безопасный метод это использовать queue.Queue, как показано выше (то есть обращаться к label, только из потока с root.mainloop(), который её создал). Но если показывать прогресс не нужно (надпись прежняя остаётся пока блокирующий запрос не завершится), то в простых случаях можно обратиться и из другого потока напрямую:
#!/usr/bin/env python3
import tkinter
from threading import Thread
import requests
def fetch(url, on_response):
requests.get(url, hooks=dict(response=on_response)) # blocking function
def on_response(response, **unused_kwargs):
# NOTE: it is called from another thread
label['text'] = response.json()['origin']
# get result in a background thread
root = tkinter.Tk()
label = tkinter.Label(font=(None, 100), text='no result yet')
label.pack()
url = 'https://httpbin.org/delay/3'
Thread(target=fetch, args=[url, on_response], daemon=True).start()
root.mainloop()
Современная реализация tkinter модуля в CPython пытается поддерживать обращение к tkinter объектам из других потоков. К примеру, если не включены потоки в Tcl интерпретаторе, то mutex расставляются,
чтобы только один поток имел доступ к Tcl интерпретатору одновременно.
Если включены потоки (tkinter.Tcl().eval('info exists ::tcl_platform(threaded)') == '1'), то обращения к Tcl из других потоков в очередь автоматически внутри tkinter добавляются и выполняются, когда управление вернётся на поток, где запущен Tcl интерпретатор (see all the gory details). Возможны баги, поэтому при возникновении любых проблем, обычно проще использовать упрощённую модель: все обращения к GUI объектам только в GUI потоке производятся.
Комментариев нет:
Отправить комментарий