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

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

  • 协程的引入
  • 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. 不通过DataRow,直接往DataTable中添加新行DataTable.LoadDataRow(object[],bool)

    DataTable dtver = new DataTable();                dtver.Columns.Add("VERSION");            ...

  2. 安装ssh-batch工具

    关于sshbatch sshbatch是用perl写了非常方便操作管理集群的一个工具,项目的源码在GitHub托管. 关于sshbatch以及其详细的使用方法,春哥在GitHub上介绍的非常详细了,详 ...

  3. [转]如何让多个不同类型的后端网站用一个nginx进行反向代理实际场景分析

    前段时间公司根据要求需要将聚石塔上服务器从杭州整体迁移到张家口,刚好趁这次机会将这些乱七八糟的服务器做一次梳理和整合,断断续续一个月迁移完 成大概优化掉了1/3的机器,完成之后遇到了一些问题,比如曾今 ...

  4. JOISC2014 Day2 E "交朋友" (思维+假的SCC)

    传送门 题目描述 你是活跃在历史幕后的一名特工,为了世界和平而夜以继日地努力着. 这个世界有N个国家,编号为1..N; 你的目的是在这N个国家之间建立尽可能多的友好关系. 你为了制定一个特工工作的计划 ...

  5. linux内核符号表

    我们已经看到 insmod 如何对应共用的内核符号来解决未定义的符号. 表中包含了全局内 核项的地址 -- 函数和变量 -- 需要来完成模块化的驱动. 当加载一个模块, 如何由模块 输出的符号成为内核 ...

  6. CF1045G AI robots

    CF1045G AI robots 题目大意就不说了 这道题可以用CDQ分治做 但是,如何选择CDQ分治的维度一直是CDQ分治的难点所在 这道题我们有三种选择 1.让智商高的数智商低的 2.让看的近的 ...

  7. CodeForces - 375D Tree and Queries (莫队+dfs序+树状数组)

    You have a rooted tree consisting of n vertices. Each vertex of the tree has some color. We will ass ...

  8. ZOJ3537 Cake

    ZOJ3537 Cake 传送门:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3537 题意: 给你几何形状的蛋糕,你需要 ...

  9. 百度人脸识别集成错误:Build command failed. Error while executing process F:\dev\Android\Sdk\cmake\3.6.4111459\bin\cmake.exe with arguments

    大概是这么个错误 Build command failed. Error while executing process F:\dev\Android\Sdk\cmake\3.6.4111459\bi ...

  10. Vijos1917 艾酱最喜欢的数字 [求绝对众数]

    1.题意:第一行一个数字N,表示一共有多少个数字,第二行N个数字,保证其中至少有一个数字出现次数超过一半,任务是求出这个出现最多的数. 2.分析:本题是明显的求众数的问题,常规思路为开一个大数组,在读 ...