马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 zltzlt 于 2019-10-7 12:53 编辑
Python 的异步协程函数 async / await
看到题目,很多鱼油可能会问:异步协程函数是什么?先别着急,马上你就知道了。先举一个例子:
1. 假设我们有三台洗衣机,现在有三批衣服需要分别放到这三台洗衣机里面洗。
相信大家都会写出这样的代码:
def wash():
from time import sleep
def washer1():
sleep(3) # 第一台洗衣机,需要洗 3 秒才能洗完(打个比方)
print('washer1 finished') # 洗完的时候,洗衣机会响一下,告诉我们洗完了
def washer2():
sleep(2)
print('washer2 finished')
def washer3():
sleep(5)
print('washer3 finished')
washer1()
washer2()
washer3()
但是,这样写虽然能正确运行,洗衣机会洗衣服,不会崩溃(报错),但效率太低,大部分时间都花在挨个地等洗衣机上了,这不是我们想要的。
2. 我们要避免无味的等待,为了提高效率,我们将使用 async(async 是 Python 3.4 新添加的关键字)。
washer1()、washer2()、washer3() 本来是普通函数,现在我们用 async 将它们升级成 “异步函数”。一个异步函数,有个更标准的称呼,我们叫它 “协程” (coroutine)。
def wash():
from time import sleep
async def washer1():
sleep(3)
print('washer1 finished')
async def washer2():
sleep(2)
print('washer2 finished')
async def washer3():
sleep(5)
print('washer3 finished')
washer1()
washer2()
washer3()
但升级成异步函数后,运行会发出警告:RuntimeWarning: coroutine 'wash.<locals>.washer1' was never awaited
RuntimeWarning: coroutine 'wash.<locals>.washer2' was never awaited
RuntimeWarning: coroutine 'wash.<locals>.washer3' was never awaited
这是为什么呢?
从正常人的理解来看,我们现在有了异步函数,但是却忘了定义应该什么时候 “离开” 一台洗衣机,去开另一个 …… 这就会导致,现在的情况是我们一边看着第一台洗衣机,一边着急地想着 “是不是该去开第二台洗衣机了呢?” 但又不敢去, 最终还是花了 10 秒的时间才把衣服洗完。
3. 现在我们吸取了上次的教训,告诉自己洗衣服的过程是 “可等待的” (awaitable)。在它开始洗衣服的时候,我们可以去弄别的机器。
def wash():
from time import sleep
async def washer1():
await sleep(3) # 注意这里加入了 await
print('washer1 finished')
async def washer2():
await sleep(2)
print('washer2 finished')
async def washer3():
await sleep(5)
print('washer3 finished')
washer1()
washer2()
washer3()
尝试运行一下,我们会发现还是会发出警告,警告内容和上面的一样。这是为什么呢?这里我给大家解释一下。
#敲黑板,重点来了!
1. 第一个问题
await 后面必须跟一个 awaitable 类型或者具有 __await__ 方法的对象。这个 awaitable 对象,并不是我们认为 time.sleep() 是 awaitable(可等待的)就可以 await 了,常见的 awaitable 对象应该是:
import asyncio
await asyncio.sleep(3)
asyncio 库的 sleep() 机制与 time.sleep() 不同,前者是 “假性睡眠”,后者是会导致线程阻塞的 “真性睡眠”。
await an_async_function()
一个异步函数(async)也是可等待的对象。
以下是不可等待的:await time.sleep(3)
await 'hello' # <class 'str'> 没有定义 __await__ 方法
await 3 + 2 # <class 'int'> 没有定义 __await__ 方法
await None # <class 'NoneType'> 没有定义 __await__ 方法
await a_sync_function() # 普通的函数是不可等待的
2. 第二个问题
如果我们要执行异步函数,不能用这样的调用方法:washer1()
washer2()
washer3()
而应该用 asyncio 库中的事件循环机制来启动(具体见下面讲解)。
4. 下面是最终我们想要的实现。
def wash():
import time
import asyncio # 引入 asyncio 库
start = time.time()
async def washer1():
await asyncio.sleep(3) # 使用 asyncio.sleep(),它返回的是一个可等待的对象
print('washer1 finished')
async def washer2():
await asyncio.sleep(2)
print('washer2 finished')
async def washer3():
await asyncio.sleep(5)
print('washer3 finished')
loop = asyncio.get_event_loop()
tasks = [
washer1(),
washer2(),
washer3(),
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
print('运行用时:', time.time() - start)
接下来我将详细讲解代码。
事件循环机制分为以下几步骤:
1. 创建一个事件循环。
loop = asyncio.get_event_loop()
2. 将异步函数加入事件队列。
tasks = [
washer1(),
washer2(),
washer3(),
]
3. 执行事件队列,直到最晚的一个事件被处理完毕后结束。
loop.run_until_complete(asyncio.wait(tasks))
PS:如果不满意想要多洗几遍,可以多写几句:
loop.run_until_complete(asyncio.wait(tasks))
loop.run_until_complete(asyncio.wait(tasks))
loop.run_until_complete(asyncio.wait(tasks))
...
4. 如果不再使用 loop,建议养成良好关闭的习惯(有点类似于文件读写结束时的 close() 操作)。
最终的打印效果:
washer2 finished
washer1 finished
washer3 finished
运行用时: 5.0031044483184814 # 毕竟切换线程也要有点耗时的
如果这篇文章让你学习到新知识,不要忘记点下面的 “评分” 作为奖励哦 ~ ^_^ |