且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

在 tkinter 的 Tk.after() 方法中使用 async/await 关键字

更新时间:2023-02-24 19:59:27

tk.after 接受普通函数,而不是协程.要将协程运行到完成,您可以使用 run_until_complete,就像您第一次一样:

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()))

此外,不要调用 loop.close(),因为您将再次需要循环.上述快速修复适用于许多用例.然而,事实是,如果 updateLoans() 由于网络缓慢或远程服务问题而需要很长时间,它将使 GUI 完全无响应.一个好的 GUI 应用会希望避免这种情况.

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.

虽然 Tkinter 和 asyncio 尚不能共享事件循环,但完全可以运行 asyncio 事件在一个单独的线程中循环.主线程然后运行 ​​GUI,而专用的 asyncio 线程运行所有 asyncio 协程.当事件循环需要通知 GUI 刷新某些内容时,它可以使用队列 如下所示一>.另一方面,如果 GUI 需要告诉事件循环做某事,它可以调用 call_soon_threadsaferun_coroutine_threadsafe.

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.