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

前端 未结 1 2053
悲&欢浪女
悲&欢浪女 2021-01-15 19:23

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 u

相关标签:
1条回答
  • 2021-01-15 19:51

    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.
    
    0 讨论(0)
提交回复
热议问题