什么是异步编程?
异步编程是指并发编程的范式,其中除了单个主应用程序线程之外,工作可以委托给一个或多个并行工作线程。这被称为非阻塞系统,其中整体系统速度不受订单执行的影响,并且多个进程可以同时发生。
函数从 API 获取数据需要时间。设计异步编程是为了适应调用函数到返回该函数的值之间的延迟。让我们通过一个例子来理解这一点 -
如果您要创建一个天气应用程序,您可能希望从一个外部 API 获取城市的温度,并从另一个 API 获取风速或降雨可能性。在同步设置中,这将以顺序方式发生,其中第二个 API 请求仅在第一个 API 请求成功完成时发出。随着请求数量的增加,这种延迟会不断增加,从而导致糟糕的用户体验。异步代码允许您以非阻塞方式实现上述内容,以便可以在等待第一个请求完成之前启动第二个请求。
Python 在 Python 3.5 中使用 async/await
语法引入了对异步代码的支持。需要明确的是,Python 采用了单线程、单进程的设计,只给人一种并行的印象,可以称为“协同多任务”。
异步编程允许用户在应用程序中进行他的业务,而进程在后台运行,从而增强了用户体验。
异步编程的工作原理
了解异步编程如何工作的最简单方法是将其与同步编程进行比较。现在来看一个同步编程的例子。
同步编程
同步编程遵循着严格的顺序。当代码在同步程序中运行时,它将遵循算法的每一步。它按顺序执行此操作,并将等待当前操作完成,然后再继续下一个操作。
同步编程遵循“做蛋糕”算法:
- 计算原料重量
- 混合面粉、鸡蛋和糖
- 加热烤箱并烘烤
- 吃蛋糕
每一步都必须按顺序进行。在烘烤混合物之前,必须测量原料,必须混合混合物。而且,要尝起来像蛋糕,应该在吃之前先烤好。因为只有一个人在做所有的工作,所以在开始下一个任务之前,您必须完全完成一项任务。同步编程具有单轨思想。它一步一步地遵循指南。
# -*- coding: utf-8 -*- """ 同步编程:做蛋糕 """ import threading import time from threading import RLock def Measure(): print("Measure the ingredients.") def Mix(): print("Mix flour, eggs, and sugar.") def Bake(): print("Bake Cake") def Eat(): print("Eat your cake") def main(): measure = threading.Thread(target=Measure) mix = threading.Thread(target=Mix) bake = threading.Thread(target=Bake) eat = threading.Thread(target=Eat) start_time = time.time() lock = RLock() lock.acquire() measure.start() time.sleep(5) lock.release() lock.acquire() mix.start() time.sleep(3) lock.release() lock.acquire() bake.start() time.sleep(10) lock.release() eat.start() end_time = time.time() print("\n做蛋糕结束,共花费了{}时间".format(end_time - start_time)) if __name__ == "__main__": main()
运行结果:
Measure the ingredients. Mix flour, eggs, and sugar. Bake Cake Eat your cake 做蛋糕结束,共花费了18.030949354171753时间
异步编程
相比之下,异步蛋糕烘焙允许多人同时完成任务。一个人可以收集和测量成分,而另一个人开始将成分混合在一起。异步编程允许启动多个进程,让进程完成它们的工作,当它们的工作完成时,它会得到结果并完成步骤。
如果烤箱在蛋糕粉完全准备好之前完成加热,异步编程表示没关系。如果没有准备好混合物,同步编程永远不会启动烤箱。混合完成后,它会向算法发送更新以返回并获取混合结果并将其推送到整个过程。现在,准备好蛋糕粉后,可以将其放入已经加热到合适温度的加热烤箱中,准备烘烤蛋糕。
不幸的是,异步编程不会帮助你吃掉你的蛋糕,但它会帮助你更快地完成蛋糕。烘烤必须在你可以吃之前发生。 (而且,如果在蛋糕准备好之前叫食者吃饭,就像在蛋糕混合物准备好之前烤箱是如何加热的,那么吃东西的人可能会在厨房里饥肠辘辘。)
异步函数
异步函数经常出现在前端应用程序中,特别是在独立的、大容量的 IO 任务中使用。前端应用程序受益于它的使用,因为它增强了应用程序的流程。
后端进程可能使用异步函数来运行许多任务或进行大量网络调用。在后端,异步编程允许计算机做更多、更快的事情。它调用许多响应时间不确定的函数并处理结果。一个例子是网络抓取,然后将结果存储在数据库中:这个过程是例行的,结果写入目录的顺序无关紧要——它们只需要有一个文件名。
典型的函数是用 async/await 组合编写的。
async function foo() { const value = await somePromise(); return value; }
常见用例
异步函数最常见的用途是调用 API。因为网络时间和检索是不确定的,异步函数会说,“从网站(或 REST API)获取数据,当它到达这里时,将获取的数据插入回我的脚本中。”
异步函数用于:与 API 交互、减慢应用程序的用户体验
它们还可用于在用户活动中造成延迟。为什么要减慢应用程序的速度?因为计算机可以以非常快的速度做事,而且在执行时,它会让用户感到不安。
因此,设计人员故意减慢应用程序的速度。一条消息几乎可以立即发送给另一个用户。通常,加载圈不是说明消息发送需要时间的必要圈。相反,它之所以存在,是因为它可以帮助用户了解正在发生的事情并让使用该应用程序感觉更舒服。
是的,由于网络延迟,有时需要几秒钟才能发送一条消息。在通过网络发送消息之前,在用户设备上编码消息也需要一些时间。
屏幕上的项目可以立即出现和消失,并且通过动画帮助用户了解屏幕上正在发生的事情。动画可以是异步的,因为当它们在一段时间内执行它们的操作时,其他功能可以在后台运行。
何时使用异步函数
异步并不总是最好的方法。异步程序增加了更多的复杂性,使代码更不可读。年轻的程序员经常会过多地使用异步函数,因为他们认为它可以作为一种保障来确保他们的代码在运行时工作。何时使用异步函数的一般规则:
- 适用于:可能需要一段时间的任务;高迭代。
- 不适用于:简单。
当涉及大量迭代或循环内的操作很复杂时,异步循环是必要的。但是对于像遍历一个小数组这样的简单任务,没有理由通过使用复杂的递归函数使事情变得过于复杂。一个简单的同步 for/while 循环工作得很好,而且速度更快,可读性更好。-- Max Galka, mapping founder of blueshift