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

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

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

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

从本篇你将了解到:

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

篇幅较长,请耐心阅读。

async/await的引入

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

为了语法更加简洁。于是,在Python3.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语法,方便了人们的理解,同时也让代码更加简洁。

推荐阅读

深入理解协程(一):协程的引入

深入理解协程(二):yield from实现异步协程

年底送福利,关注公众号【西加加先生】,2020一起玩Python,公众号回复【2020】即可获取抽奖方式参与抽奖

深入理解协程(三):async/await实现异步协程的更多相关文章

  1. Python PEP 492 中文翻译——协程与async/await语法

    原文标题:PEP 0492 -- Coroutines with async and await syntax 原文链接:https://www.python.org/dev/peps/pep-049 ...

  2. 理解ES7中的async/await

    理解ES7中的async/await 优势是:就是解决多层异步回调的嵌套 从字面上理解 async/await, async是 "异步"的含义,await可以认为是 async w ...

  3. Atitit. Async await 优缺点 异步编程的原理and实现 java c# php

    Atitit. Async await 优缺点 异步编程的原理and实现 java c# php 1. async & await的来源1 2. 异步编程history1 2.1. 线程池 2 ...

  4. [.NET] 利用 async & await 的异步编程

    利用 async & await 的异步编程 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/5922573.html  目录 异步编程的简介 异 ...

  5. [.NET] 利用 async & await 进行异步 IO 操作

    利用 async & await 进行异步 IO 操作 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6082673.html  序 上次,博主 ...

  6. 利用 async & await 的异步编程

    走进异步编程的世界 - 开始接触 async/await 利用 async & await 的异步编程 async 的三大返回类型 公司技术需求备忘录

  7. async+await处理异步问题

    在编写网页的时候我们常常会遇到异步问题,async+await是es6提出的解决异步的方法,下面我们来看看这个方法怎么实现解决异步的, 大家都知道,setTimeout是一个定时器.他是一个异步执行的 ...

  8. 使用ES6新特性async await进行异步处理

    我们往往在项目中会遇到这样的业务需求,就是首先先进行一个ajax请求,然后再进行下一个ajax请求,而下一个请求需要使用上一个请求得到的数据,请求少了还好说,如果多了,就要一层一层的嵌套,就好像有点c ...

  9. Python的异步编程[0] -> 协程[0] -> 协程和 async / await

    协程 / Coroutine 目录 生产者消费者模型 从生成器到异步协程– async/await 协程是在一个线程执行过程中可以在一个子程序的预定或者随机位置中断,然后转而执行别的子程序,在适当的时 ...

随机推荐

  1. SSM整合 上传下载之添加商品

    上传下载细节: 导入xml配置文件!! Controller中要配置存储路径,调用transferto上传文件 上传图片 要将图片的类设置为 MultipartFile 图片下载: 源码: 页面展示: ...

  2. Hibernate @OneToOne懒加载实现解决方案

    在hibernate注解(三)中,我提高过一对一(@OneToOne)懒加载失效的问题.虽然给出了解决方法,但并没有给出完整的解决方案.今天我专门针对该问题进行讨论.至于懒加载失效的原因,在之前的文章 ...

  3. [转]基于VS Code快速搭建Java项目

    有时候随手想写一点Java测试代码,以控制台程序为主,还会用到一些其它框架,并基于Maven构建. 1.Java Extension Pack一定要安装. 2.VS Code打开一个指定目录,创建相应 ...

  4. dotnet core 2.1 使用阶梯编译

    在 dotnet core 2.1 可以使用阶梯编译的方法,从 dotnet framework 开始,在代码的所有方法在第一次进入的时候就需要使用 JIT 进行编译为本机的代码.可以看到代码是在第一 ...

  5. 【b804】双栈排序

    Time Limit: 1 second Memory Limit: 50 MB [问题描述] Tom最近在研究一个有趣的排序问题.如图所示,通过2个栈S1和S2,Tom希望借助以下4种操作实现将输入 ...

  6. python面向对象之三大特性

    继承 先看个简单的例子了解一下继承. class Animal: # 父类 def __init__(self, name, age, department): self.name = name se ...

  7. [Ramda] Handle Errors in Ramda Pipelines with tryCatch

    Handling your logic with composable functions makes your code declarative, leading to code that's ea ...

  8. H3C IPv6地址分类

  9. Spring Data JPA坑点记录

    本篇进行Spring-data-jpa的介绍,几乎涵盖该框架的所有方面,在日常的开发当中,基本上能满足所有需求.这里不讲解JPA和Spring-data-jpa单独使用,所有的内容都是在和Spring ...

  10. Linux 内核 struct device 设备

    在最低层, Linux 系统中的每个设备由一个 struct device 代表: struct device { struct device *parent; struct kobject kobj ...