Страницы

Поиск по вопросам

четверг, 4 апреля 2019 г.

Как сделать постоянное обновление окна Tkinter? Как избежать подвисания на время ожидания ответа от сервера

Всем привет! для обновления окна использую
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 потоке производятся.

Комментариев нет:

Отправить комментарий