深入理解协程(三):async/await实现异步协程

栏目: IT技术 · 发布时间: 4年前

内容简介:原创不易,转载请联系作者本篇为

原创不易,转载请联系作者

深入理解协程 分为三部分进行讲解:

  • 协程的引入
  • yield from实现异步协程
  • async/await实现异步协程

本篇为 深入理解协程 系列文章的 最后一篇

从本篇你将了解到:

  1. async/await 的使用。
  2. 如何从 yield from 风格的协程修改为 async/await 风格。

篇幅较长,请耐心阅读。

async/await的引入

上篇 【yield from实现异步协程】 我们引入了 asynico 模块,结合 yield from 实现异步协程。但语法不够简洁,其中涉及的 生成器装饰器 也让人头疼不已。

为了语法更加简洁。于是,在 Python 3.5(PEP 492)中新增了 async/await 语法来实现异步协程。

async/await的使用

先介绍几个概念:

  • async/await :python3.5之后用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。
  • event_loop :事件循环,程序开启一个无限的循环,程序员会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
  • coroutine :协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
  • task :任务,是对协程进一步封装,其中包含任务的各种状态。
  • future : 代表将来执行或没有执行的任务的结果。它和task上没有本质的区别

1.创建协程

在def前加上async的声明,就完成了一个协程函数的定义。 协程函数不能直接调用运行,需要将协程注册到事件循环,并启动事件循环才能使用

import asyncio

async def fun(a):    # 定义协程函数
    print(a)

# 调用协程函数,生成一个协程对象,此时协程函数并未执行
coroutine = fun('hello world')
# 创建事件循环
loop = asyncio.get_event_loop()
# 将协程函数添加到事件循环,并启动
loop.run_until_complete(coroutine)

# 输出
hello word

2. 任务对象task

协程对象不能直接运行,在注册事件循环的时候,其实是run_until_complete方法将协程包装成为了一个任务(task)对象。我们也可以显式实现它。

实现方式1:

使用create_task()创建task。

import asyncio

async def fun(a):
    print(a)
    return a

coroutine = fun('hello world')
loop = asyncio.get_event_loop()
# 使用create_task()创建task,并将coroutine对象转化成task对象
task = loop.create_task(coroutine)
print(f'task: {task}')
loop.run_until_complete(task)
print(f'task: {task}')
print(f'result: {result}')

输出结果:

task: <Task pending coro=<fun() running at D:/test.py:3>>
hello world
task: <Task finished coro=<fun() done, defined at D:/test.py:3> result='hello world'>

从输出结果能看到,创建task对象后,未将task添加到事件循环之前,状态是 pending ;task对象执行完毕后,状态是 finished ,并将参数 a 的值返回。

实现方式2:

使用asyncio 的 ensure_future() 方法,创建task。

import asyncio

async def fun(a):
    print(a)
    return a

coroutine = fun('hello world')
# 使用asyncio 的 ensure_future() 方法,创建task,并将coroutine对象转化成task对象
task = asyncio.ensure_future(coroutine)
loop = asyncio.get_event_loop()
print(f'task: {task}')
loop.run_until_complete(task)
print(f'task: {task}')

输出结果:

task: <Task pending coro=<fun() running at D:/test.py:3>>
hello world
task: <Task finished coro=<fun() done, defined at D:/test.py:3> result='hello world'>

通过 ensure_future() 可以在 loop 未定义前创建task。实现效果与上面相同。

3.绑定回调函数

如果需要在task执行完毕后对结果进行处理,可以通过给task绑定回调函数完成,回调的最后一个参数是future对象(如task对象)。

import asyncio

async def fun(a):
    print(a)
    return a

def callback(task): # 回调函数,打印task的返回值
    print(f'result: {task.result()}')

coroutine = fun('hello world')
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
task.add_done_callback(callback)    #绑定回调函数
print(f'task: {task}')
loop.run_until_complete(task)
print(f'task: {task}')

输出结果:

task: <Task pending coro=<fun() running at D:/test.py:3> cb=[callback() at D:/Study/Python/python_text/非项目/协程.py:7]>
hello world
result: hello world # 完成了返回值的打印
task: <Task finished coro=<fun() done, defined at D:/test.py:3> result='hello world'>

4.多任务协程

如果我们需要执行多个任务时,我们可以定义一个任务列表,并将需要完成的协程任务都加进去。将原本的 loop.run_until_complete(tasks) 改为 loop.run_until_complete(asyncio.wait(tasks))

如果执行的是多个 耗时 的任务,如网络请求、文件读取等。此时就 await 就派上用场了, await 可以针对耗时的操作进行挂起,就像生成器里的yield一样,函数让出控制权。 协程遇到await,事件循环将会挂起该协程,执行别的协程,直到其他的协程也挂起或者执行完毕,再进行下一个协程的执行

举个例子:

import time
import asyncio

async def taskIO_1():   
    print('开始运行IO任务1...')
    await asyncio.sleep(2)  
    print('IO任务1已完成,耗时2s')
    return taskIO_1.__name__

async def taskIO_2():       
    print('开始运行IO任务2...')
    await asyncio.sleep(3)  
    print('IO任务2已完成,耗时3s')
    return taskIO_2.__name__

if __name__ == '__main__':
    start = time.time()
    loop = asyncio.get_event_loop() 
    tasks = [taskIO_1(), taskIO_2()]
    loop.run_until_complete(asyncio.wait(tasks)) # 完成事件循环,直到最后一个任务结束
    print('所有IO任务总耗时%.5f秒' % float(time.time()-start))
    
# 输出
开始运行IO任务2...
开始运行IO任务1...
IO任务1已完成,耗时2s
IO任务2已完成,耗时3s
所有IO任务总耗时3.00251秒

可以看出,原本需要5秒,现在执行只需要3秒。

yield from转async/await

上述代码有没有很眼熟。

其实,这段代码正是 【yield from实现异步协程】 末尾, yield from 结合 asynico 实现异步协程的代码。只是将yielf from风格变为async/await风格。

由于 async/awaityield from 风格的协程底层实现方式相同。因此,从 yield from 风格改为 async/await 风格非常容易。只需:

  • @asyncio.coroutine 替换为 async
  • yield from 替换为 await

async/await 风格的代码隐藏了装饰器、 yield from 语法,方便了人们的理解,同时也让代码更加简洁。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

ASP.NET AJAX in Action

ASP.NET AJAX in Action

Alessandro Gallo、David Barkol、Rama Vavilala / Manning Publications / 2007-9-3 / USD 44.99

Ajax has revolutionized the way users interact with web pages today. Gone are frustrating page refreshes, lost scroll positions and intermittent interaction with a web site. Instead, we have a new gen......一起来看看 《ASP.NET AJAX in Action》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具