教程 8 - 使其光滑

除非您的网络连接速度*快,否则您可能会注意到,当您按下按钮时,应用程序的图形用户界面会锁定一会儿。这是因为我们发出的网络请求是*同步*的。当我们的应用程序发出网络请求时,它会等待应用程序接口返回响应,然后再继续。在等待的过程中,应用程序*不允许重新绘制,结果导致应用程序锁定。

图形用户界面事件循环

要理解为什么会出现这种情况,我们需要深入了解图形用户界面应用程序的工作原理。具体细节因平台而异,但无论使用何种平台或图形用户界面环境,高层概念都是相同的。

从根本上说,图形用户界面应用程序就是一个单一的循环,看起来就像::

while not app.quit_requested():
    app.process_events()
    app.redraw()

这个循环被称为 Event Loop。(这些并不是实际的方法名称,而是 “伪代码 “的说明)。

当你点击一个按钮、拖动一个滚动条或输入一个键时,你就产生了一个 “事件”。该 “事件 “被放入一个队列,应用程序将在下一次有机会时处理队列中的事件。为响应事件而触发的用户代码称为*事件处理程序*。这些事件处理程序作为``process_events()``调用的一部分被调用。

应用程序处理完所有可用事件后,就会 “重绘() “图形用户界面。这将考虑到事件对应用程序显示所造成的任何变化,以及操作系统中发生的任何其他情况,例如,其他应用程序的窗口可能会遮挡或显示我们应用程序的部分窗口,而我们应用程序的重绘将需要反映当前可见的窗口部分。

需要注意的重要细节是:当应用程序在处理事件时,不能重绘也不能处理其他事件

这意味着事件处理程序中包含的任何用户逻辑都需要快速完成。完成事件处理程序的任何延迟都会被用户观察到,表现为图形用户界面更新的减慢(或停止)。如果延迟时间足够长,操作系统可能会将此报告为问题–macOS 的 “沙滩球 “和 Windows 的 “旋转器 “图标就是操作系统在告诉你,你的应用程序在事件处理程序中耗时过长。

像 “更新标签 “或 “重新计算输入总和 “这样的简单操作很容易快速完成。然而,有很多操作是无法快速完成的。如果要执行复杂的数学计算,或为文件系统中的所有文件编制索引,或执行大型网络请求,就不能 “快速完成”–这些操作本身就很慢。

那么,我们如何在图形用户界面应用程序中执行长期操作呢?

异步编程

我们需要的是一种方法,让处于长期事件处理程序中间的应用程序知道,只要我们能从上次中断的地方继续运行,就可以暂时将控制权释放回事件循环。应用程序可以自行决定何时释放控制权;但如果应用程序定期向事件循环释放控制权,我们就可以拥有一个长期运行的事件处理程序,并**保持响应式用户界面。

我们可以通过使用*异步编程*来实现这一点。异步编程是一种描述程序的方法,它允许解释器同时运行多个函数,在所有并发运行的函数之间共享资源。

异步函数(称为 * 协同例程*)需要明确声明为异步函数。它们还需要在内部声明何时有机会将上下文切换到另一个共同例程。

在 Python 中,异步编程是通过 asyncawait 关键字以及标准库中的 asyncio 模块来实现的。async` 关键字允许我们声明一个函数是异步协程。关键字 await 提供了一种方法来声明何时有机会将上下文切换到另一个协程。asyncio 模块为异步编码提供了一些其他有用的工具和原语。

使教程异步化

为了使我们的教程成为异步教程,请修改事件处理程序 “say_hello()”,使其看起来像这样::

async def say_hello(self, widget):
    async with httpx.AsyncClient() as client:
        response = await client.get("https://jsonplaceholder.typicode.com/posts/42")

    payload = response.json()

    self.main_window.info_dialog(
        greeting(self.name_input.value),
        payload["body"],
    )

与上一版本相比,该代码只有 4 处改动:

  1. 方法定义为 asyncdef,而不只是 def。这告诉 Python 该方法是一个异步协程。

  2. 创建的客户端是异步的 AsyncClient() 而不是同步的 Client()`。这就告诉 ``httpx 应在异步模式而非同步模式下运行。

  3. 用于创建客户端的上下文管理器被标记为 async。这就告诉 Python,当上下文管理器进入和退出时,有机会释放控制。

  4. get “调用带有 “await “关键字。这指示应用程序在等待网络响应时,可以将控制权释放给事件循环。

Toga 允许你使用常规方法或异步协程作为处理程序;Toga 在幕后管理一切,确保处理程序按要求被调用或等待。

如果保存这些更改并重新运行应用程序(在开发模式下使用 briefcase dev 或更新并重新运行打包的应用程序),应用程序不会有任何明显的变化。不过,当您点击按钮触发对话框时,您可能会注意到一些细微的改进:

  • 按钮会返回到 “未点击 “状态,而不是停留在 “已点击 “状态。

  • 沙滩球”/”旋转器 “图标不会出现

  • 如果在等待对话框出现时移动/调整应用程序窗口的大小,窗口将重新绘制。

  • 如果尝试打开应用程序菜单,菜单会立即出现。

下一步

现在,我们已经有了一个既流畅又反应灵敏的应用程序,即使在等待速度较慢的应用程序接口时也是如此。但是,我们如何确保在继续进一步开发的过程中,应用程序仍能正常运行?如何测试应用程序?请访问 Tutorial 9 了解详情…