跳到主要内容

异步应用 Async apps

Flet 应用程序可以以异步方式编写,并使用 asyncio 和其他 Python 异步库。调用协程在 Flet 中是天然支持的,因此您不需要将它们包装成同步运行。

默认情况下,Flet 使用 threading 库在单独的线程中运行用户会话并执行事件处理程序,但有时在等待 HTTP 响应或执行 sleep() 时,这可能会导致 CPU 的效率低下。

另一方面,Asyncio 允许在单个线程中实现并发性,通过在“协程”之间切换执行上下文。这对于将要使用 Pyodide 发布为静态网站的应用程序特别重要。Pyodide 是作为 WebAssembly (WASM) 构建的 Python 运行时,运行在浏览器中。在撰写本文时,它尚未完全支持 threading

异步入门

要开始使用异步 Flet 应用程序,您应该将 main() 方法声明为 async

import flet as ft

async def main(page: ft.Page):
await page.add_async(ft.Text("Hello, async world!"))

ft.app(main)

如果 Flet 应用程序是较大应用程序的一部分并且从 async 代码中调用,则可以使用 await ft.app_async(main)

请注意,使用 await page.add_async(...) 将新控件添加到页面中。在异步应用程序中,您不能再使用 page.add() 或其他同步页面方法 - 必须在代码中的所有位置都使用它们以 _async 结尾的异步对应方法:

  • page.add()await page.add_async()
  • page.update()await page.update_async()
  • page.clean()await page.clean_async()
  • 等等。

控件事件处理程序

控件事件处理程序可以是同步的,也可以是 async 的。

如果处理程序不调用任何异步方法,则可以是常规的同步方法:

def page_resize(e):
print("New page size:", page.window_width, page.window_height)

page.on_resize = page_resize

然而,如果处理程序调用异步逻辑,则它必须是 async 的:

async def main(page: ft.Page):
async def button_click(e):
await page.add_async(ft.Text("Hello!"))

await page.add_async(ft.ElevatedButton("Say hello!", on_click=button_click))


ft.app(main)

异步 lambda

在 Python 中没有异步 lambda。对于简单的事情,在异步应用程序中可以使用 lambda 事件处理程序:

page.on_error = lambda e: print("Page error:", e.data)

但是您不能使用异步 lambda,因此必须使用异步事件处理程序。

延迟执行

要在异步 Flet 应用程序中延迟代码执行,应该使用 asyncio.sleep(),而不是 time.sleep(),例如:

import asyncio
import flet as ft

async def main(page: ft.Page):
async def button_click(e):
await asyncio.sleep(1)
await page.add_async(ft.Text("Hello!"))

await page.add_async(
ft.ElevatedButton("Say hello with delay!", on_click=button_click)
)

ft.app(main)

线程

从技术上讲,您可以在异步应用程序中使用 threading 库,但这将是一个坏主意。Flet API 使用的 asyncio 版本的锁、队列和任务不是线程安全的,例如,从多个线程调用 await page.update_async() 将导致结果不可预测。另外,如果决定将应用程序 部署为静态网站,则 Pyodide 不支持 threading 库。

要在后台运行某些内容,可以使用 asyncio.create_task()。例如,来自 用户控件 指南的 "倒计时" 控件的异步版本将如下所示:

import asyncio
import flet as ft

class Countdown(ft.UserControl):
def __init__(self, seconds):
super().__init__()
self.seconds = seconds

async def did_mount_async(self):
self.running = True
asyncio.create_task(self.update_timer())

async def will_unmount_async(self):
self.running = False

async def update_timer(self):
while self.seconds and self.running:
mins, secs = divmod(self.seconds, 60)
self.countdown.value = "{:02d}:{:02d}".format(mins, secs)
await self.update_async()
await asyncio.sleep(1)
self.seconds -= 1

def build(self):
self.countdown = ft.Text()
return self.countdown

async def main(page: ft.Page):
await page.add_async(Countdown(120), Countdown(60))

ft.app(target=main)