Using async/await keywords with Tk.after() method of tkinter

半城伤御伤魂 提交于 2020-01-24 21:44:08

问题


I am creating a cryptocurrency exchange API client using Python3.5 and Tkinter. I have several displays that I want to update asynchronously every 10 seconds. I am able to update the displays every 10 seconds using Tk.after() like in this example

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)

In order for the after method to continue to update continuously every 10 seconds the function updateLoans() needs to be passed as a callable into after() inside of the function.

Now the part that is stumping me, when I make this function asynchronous with python's new async and await keywords

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)

The problem here is that I can not await a callable inside of the parameters for the after method. So I get a runtime warning. RuntimeWarning: coroutine 'updateLoans' was never awaited.

My initial function call IS placed inside of an event loop.

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

The display populates just fine initially but never updates.

How can I use Tk.after to continuously update a tkinter display asynchronously?


回答1:


tk.after accepts a normal function, not a coroutine. To run the coroutine to completion, you can use run_until_complete, just as you did the first time:

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

Also, don't call loop.close(), since you'll need the loop again.


The above quick fix will work fine for many use cases. The fact is, however, that it will render the GUI completely unresponsive if updateLoans() takes a long time due to slow network or a problem with the remote service. A good GUI app will want to avoid this.

While Tkinter and asyncio cannot share an event loop yet, it is perfectly possible to run the asyncio event loop in a separate thread. The main thread then runs the GUI, while a dedicated asyncio thread runs all asyncio coroutines. When the event loop needs to notify the GUI to refresh something, it can use a queue as shown here. On the other hand, if the GUI needs to tell the event loop to do something, it can call call_soon_threadsafe or run_coroutine_threadsafe.

Example code (untested):

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.


来源:https://stackoverflow.com/questions/49958180/using-async-await-keywords-with-tk-after-method-of-tkinter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!