Использование ключевых слов async / wait с использованием метода Tk.after () tkinter

Riley Hughes спросил: 28 марта 2018 в 03:23 в: python

Я создаю клиент API обмена криптовалютами, используя Python3.5 и Tkinter. У меня есть несколько дисплеев, которые я хочу обновлять асинхронно каждые 10 секунд. Я могу обновлять дисплеи каждые 10 секунд, используя Tk.after(), как в этом примере

def updateLoans():
    offers = dd.loanOffers()
    demands = dd.loanDemands()
    w.LoanOfferView.delete(1.0, END)
    w.LoanDemandView.delete(1.0, END)
    w.LoanOfferView.insert(END, offers)
    w.LoanDemandView.insert(END, demands)
    print('loans refreshed')    root.after(10000, updateLoans)

Для продолжения использования метода after для обновления непрерывно каждые 10 секунд функция updateLoans() должна быть передана как вызываемая в after() внутри функции.

Теперь часть, которая меня точит, когда я делаю эту функцию асинхронной с новым асинхронным python и ожидаю ключевые слова

async def updateLoans():
    offers = await dd.loanOffers()
    demands = await dd.loanDemands()
    w.LoanOfferView.delete(1.0, END)
    w.LoanDemandView.delete(1.0, END)
    w.LoanOfferView.insert(END, offers)
    w.LoanDemandView.insert(END, demands)
    print('loans refreshed')    root.after(10000, updateLoans)

Проблема заключается в том, что я не могу ждать вызываемой внутри параметров для after метод. Поэтому я получаю предупреждение о времени выполнения. RuntimeWarning: coroutine 'updateLoans' was never awaited.

Мой начальный вызов функции IS помещается внутри цикла события.

loop = asyncio.get_event_loop()
loop.run_until_complete(updateLoans())
loop.close()

Дисплей сначала заполняется, но никогда не обновляется.

Как я могу использовать Tk.after для непрерывного обновления отображения tkinter асинхронно?

1 ответ

Есть решение
user4815162342 ответил: 29 марта 2018 в 03:29

tk.after принимает нормальную функцию, а не сопрограмму. Чтобы выполнить сопрограмму до завершения, вы можете использовать run_until_complete, как вы делали это в первый раз:

loop = asyncio.get_event_loop()
root.after(10000, lambda: loop.run_until_complete(updateLoans()))

Кроме того, не вызывайте loop.close(), поскольку вам снова понадобится цикл.


Приведенное выше быстрое исправление отлично подойдет для многих случаев использования. Однако дело в том, что графический интерфейс будет полностью не отвечать, если updateLoans() занимает много времени из-за медленной сети или проблемы с удаленным сервисом. Хорошее приложение с графическим интерфейсом захочет избежать этого.

Хотя Tkinter и asyncio пока не могут совместно использовать цикл событий, вполне возможно запустить цикл событий asyncio в отдельном потоке. Затем основной поток запускает графический интерфейс, а выделенный поток asyncio - все сопрограммы asyncio. Когда цикл обработки событий должен уведомить графический интерфейс для обновления чего-либо, он может использовать очередь, как показано здесь. С другой стороны, если GUI необходимо сообщить циклу событий что-то сделать, он может вызвать call_soon_threadsafe или run_coroutine_threadsafe.

Пример кода (не проверено):

gui_queue = queue.Queue()async def updateLoans():
    while True:
        offers = await dd.loanOffers()
        demands = await dd.loanDemands()
        print('loans obtained')
        gui_queue.put(lambda: updateLoansGui(offers, demands))
        await asyncio.sleep(10)def updateLoansGui(offers, demands):
    w.LoanOfferView.delete(1.0, END)
    w.LoanDemandView.delete(1.0, END)
    w.LoanOfferView.insert(END, offers)
    w.LoanDemandView.insert(END, demands)
    print('loans GUI refreshed')# http://effbot.org/zone/tkinter-threads.htm
def periodicGuiUpdate():
    while True:
        try:
            fn = gui_queue.get_nowait()
        except queue.Empty:
            break
        fn()
    root.after(100, periodicGuiUpdate)# Run the asyncio event loop in a worker thread.
def start_loop():
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    loop.create_task(updateLoans())
    loop.run_forever()
threading.Thread(target=start_loop).start()# Run the GUI main loop in the main thread.
periodicGuiUpdate()
root.mainloop()# To stop the event loop, call loop.call_soon_threadsafe(loop.stop).
# To start a coroutine from the GUI, call asyncio.run_coroutine_threadsafe.
Riley Hughes ответил: 29 марта 2018 в 12:51
Один из самых ясных и подробных ответов, которые я получил о переполнении стека. Спасибо, это очень помогло
Riley Hughes ответил: 29 марта 2018 в 06:36
Поэтому, пытаясь реализовать ваше второе решение, я продолжаю получать эту ошибку. RuntimeError: There is no current event loop in thread 'Thread-1'
user4815162342 ответил: 29 марта 2018 в 03:29
@RileyHughes Хорошо, в Python 3.5 вам нужно будет дополнительно установить цикл обработки событий для нового потока. Я сейчас исправил ответ.