一、介绍

asyncio 是python3.4 引入的一个新的并发模块,主要通过使用coroutines 和 futures 来让我们更容易的去实现异步的功能,并且几乎和写同步代码一样的写代码,还没有烦人的回调。

在2018年6月 3.7的更新中针对asyncio的api进行了一些升级,主要是关于task的管理以及 event loops 方面。后面会把3.7的增加的新特性专门整理一篇文章。

现状: 其实目前来说asyncio相关的异步库并不完善,官网也并没有专门维护,在github上有一个俄罗斯的小组在开发维护一些常用的库如:aiomysql, aiopika, aioredis等。 这里有一点需要在这里提前说明:如果目前想要用asyncio异步的功能,那么你整个代码中其他的库也要是异步的而不能是阻塞的,如果我们需要用aiomysql 而不能用pymysql, 我们需要用aiohttp 而不能使用requests等等等。如果恰巧你用的一些库,现在并没有相对应的异步库,那么可能就比较麻烦了。

二、Threads, loops, coroutines and futures

  1. event loop:主要负责管理和分发不同task的执行,我们可以将不同的任务注册在event loop上。
  2. coroutines: 我们通常也称之为协程,是与python生成器类似的特殊的函数,在这个函数中通常会有一个关键字await ,当coroutine执行到await 的时候,就会将控制权释放给event loop. 如果一个coroutine被包装成一个Future类型的Task中,那么这个coroutine就需要被event loop 去调度执行
  3. futures:代表将来执行或没有执行的任务的结果,当然这个结果可能是一个异常

三、同步VS异步

asyncio 允许我们将子任务定义为coroutine,并允许你来调度它们,而在多线程中,这个调度通常是交给操作系统控制我们并不能控制。我们先通过下面的一个例子理解:

import asyncio
async def foo():
print("running in foo")
await asyncio.sleep(0)
print("back foo")
async def bar():
print("running in bar")
await asyncio.sleep(0)
print("back bar")
async def main():
tasks = [foo(), bar()]
await asyncio.gather(*tasks)
asyncio.run(main())

上述代码的运行结果如下:

running in foo
running in bar
back foo
back bar

针对上述代码的一个说明:

  1. 切记这里的sleep只能用asyncio里面的,不能直接用sleep。这里我们看到coroutine通过await的方式将控制权交还给了event loop,并切换到计划执行的下一个任务
  2. 关于gather的使用这里可以暂时忽略,后面文章会详细说明
  3. 最后使用的asyncio.run是3.7更新的新方法,负责创建一个事件循环并调度coroutine,在3.7之前是需要我们手动创建loop:asyncio.new_event_loop()

当我们的代码是同步执行的时候,执行的顺序是线性的,如果我们是异步的,顺序就变得不确定了,我们通过一个简单的爬虫的例子来理解:

import time
import random
import asyncio
import aiohttp
URL = 'https://baidu.com'
MAX_CLIENTS = 3
async def aiohttp_get(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return response
async def fetch_async(pid):
start = time.time()
sleepy_time = random.randint(2, 5)
print('fetch coroutine {} started, sleeping for {} seconds'.format(
pid, sleepy_time))
response = await aiohttp_get(URL)
datetime = response.headers.get('Date')
# 这里增加的asyncio.sleep是为了模拟每个请求有一定延迟返回
await asyncio.sleep(sleepy_time)
response.close()
return 'coroutine {}: {}, took: {:.2f} seconds'.format(
pid, datetime, time.time() - start)
async def main():
start = time.time()
futures = [fetch_async(i) for i in range(1, MAX_CLIENTS + 1)]
for i, future in enumerate(asyncio.as_completed(futures)):
result = await future
print('{} {}'.format(">>" * (i + 1), result))
print("all took: {:.2f} seconds".format(time.time() - start))
asyncio.run(main())

上述代码中,我们在每个请求里都添加了asyncio.sleep的操作,这里其实是为了模拟实际情况中当我们请求多个网站的时候,因为网络和目标网站的不同,请求返回的时间一般不同。 运行结果如下:

fetch coroutine 2 started, sleeping for 5 seconds
fetch coroutine 1 started, sleeping for 3 seconds
fetch coroutine 3 started, sleeping for 4 seconds
>> coroutine 1: Wed, 27 Feb 2019 11:27:58 GMT, took: 3.09 seconds
>>>> coroutine 3: Wed, 27 Feb 2019 11:27:58 GMT, took: 4.08 seconds
>>>>>> coroutine 2: Wed, 27 Feb 2019 11:27:58 GMT, took: 5.12 seconds
all took: 5.12 seconds

关于return_when参数 这个参数是当我们执行多个任务的时候,我只关注最快返回结果的那个任务,用法例子如下(注意我这里为了让复现一个错误,先用了python3.7之前创建loop的方法):

import time
import random
import asyncio
import aiohttp
from concurrent.futures import FIRST_COMPLETED
URL = 'https://baidu.com'
MAX_CLIENTS = 3
async def aiohttp_get(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return response
async def fetch_async(pid):
start = time.time()
sleepy_time = random.randint(2, 5)
print('fetch coroutine {} started, sleeping for {} seconds'.format(
pid, sleepy_time))
response = await aiohttp_get(URL)
datetime = response.headers.get('Date')
# 这里增加的asyncio.sleep是为了模拟每个请求有一定延迟返回
await asyncio.sleep(sleepy_time)
response.close()
return 'coroutine {}: {}, took: {:.2f} seconds'.format(
pid, datetime, time.time() - start)
async def main():
start = time.time()
futures = [fetch_async(i) for i in range(1, MAX_CLIENTS + 1)]
done, pending = await asyncio.wait(
futures, return_when=FIRST_COMPLETED
)
print(done.pop().result())
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

运行结果会出现如下情况:

fetch coroutine 2 started, sleeping for 2 seconds
fetch coroutine 1 started, sleeping for 5 seconds
fetch coroutine 3 started, sleeping for 2 seconds
coroutine 2: Wed, 27 Feb 2019 11:41:19 GMT, took: 2.11 seconds
Task was destroyed but it is pending!
task: <Task pending coro=<fetch_async() done, defined at e:/vs_python/lean_asyncio/ex2.py:17> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x00000000038E5798>()]>>

其实这里出现这种问题的原因,我们很容易理解,我们开启了三个任务,当我们收到最快完成的那个之后就关闭了循环,后面的两个任务还处于pending状态,asyncio 认为这是一个错误,所以打印出了我们看到的那个警告:Task was destroyed but it is pending! 我们如何解决这个问题呢?

四、关于future

future有四种状态:

  1. Pending
  2. Running
  3. Done
  4. Cancelled

我们可以通过调用done, cancelled 或者 running 来看当前future是否处于该状态,这里再次提醒,done 状态可以表示返回结果,也可以表示跑出了异常。我们也可以通过调用cancel来专门取消future,不过在python3.7之后,asyncio.run替我们做了这些事情,我们把上面的那个出现Task was destroyed but it is pending!的代码进行更改:

import time
import random
import asyncio
import aiohttp
from concurrent.futures import FIRST_COMPLETED
URL = 'https://baidu.com'
MAX_CLIENTS = 3
async def aiohttp_get(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return response
async def fetch_async(pid):
start = time.time()
sleepy_time = random.randint(2, 5)
print('fetch coroutine {} started, sleeping for {} seconds'.format(
pid, sleepy_time))
response = await aiohttp_get(URL)
datetime = response.headers.get('Date')
# 这里增加的asyncio.sleep是为了模拟每个请求有一定延迟返回
await asyncio.sleep(sleepy_time)
response.close()
return 'coroutine {}: {}, took: {:.2f} seconds'.format(
pid, datetime, time.time() - start)
async def main():
start = time.time()
futures = [fetch_async(i) for i in range(1, MAX_CLIENTS + 1)]
done, pending = await asyncio.wait(
futures, return_when=FIRST_COMPLETED
)
print(done.pop().result())
asyncio.run(main())

运行结果如下,完全正常了:

fetch coroutine 2 started, sleeping for 5 seconds
fetch coroutine 3 started, sleeping for 2 seconds
fetch coroutine 1 started, sleeping for 2 seconds
coroutine 3: Wed, 27 Feb 2019 11:54:13 GMT, took: 2.07 seconds

future还有一个实用的功能:允许我们在future变成完成状态时添加callback回调.

关于future的完成时结果的获取,通过下面代码来演示:

import time
import random
import asyncio
import aiohttp
from concurrent.futures import FIRST_COMPLETED
URL = 'https://httpbin.org/get'
MAX_CLIENTS = 3
async def aiohttp_get(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return response
async def fetch_async(pid):
start = time.time()
sleepy_time = random.randint(2, 5)
print('fetch coroutine {} started, sleeping for {} seconds'.format(
pid, sleepy_time))
response = await aiohttp_get(URL)
datetime = response.headers.get('Date')
# 这里增加的asyncio.sleep是为了模拟每个请求有一定延迟返回
await asyncio.sleep(sleepy_time)
response.close()
return 'coroutine {}: {}, took: {:.2f} seconds'.format(
pid, datetime, time.time() - start)
async def main():
start = time.time()
futures = [fetch_async(i) for i in range(1, MAX_CLIENTS + 1)]
done, pending = await asyncio.wait(
futures
)
print(done)
for future in done:
print(future.result())
asyncio.run(main())

运行结果如下:

fetch coroutine 2 started, sleeping for 5 seconds
fetch coroutine 1 started, sleeping for 2 seconds
fetch coroutine 3 started, sleeping for 4 seconds
{<Task finished coro=<fetch_async() done, defined at e:/vs_python/lean_asyncio/ex2.py:17> result='coroutine 3:... 5.31 seconds'>, <Task finished coro=<fetch_async() done, defined at e:/vs_python/lean_asyncio/ex2.py:17> result='coroutine 1:... 3.34 seconds'>, <Task finished coro=<fetch_async() done, defined at e:/vs_python/lean_asyncio/ex2.py:17> result='coroutine 2:... 6.38 seconds'>}
coroutine 3: Wed, 27 Feb 2019 12:10:15 GMT, took: 5.31 seconds
coroutine 1: Wed, 27 Feb 2019 12:10:15 GMT, took: 3.34 seconds
coroutine 2: Wed, 27 Feb 2019 12:10:15 GMT, took: 6.38 seconds

我们可以看到,当所有任务完成后,我们可以通过done获取每个人的结果信息。

我们也可以给我们的任务添加超时时间

import time
import random
import asyncio
import aiohttp
from concurrent.futures import FIRST_COMPLETED
URL = 'https://httpbin.org/get'
MAX_CLIENTS = 3
async def aiohttp_get(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return response
async def fetch_async(pid):
start = time.time()
sleepy_time = random.randint(2, 5)
print('fetch coroutine {} started, sleeping for {} seconds'.format(
pid, sleepy_time))
response = await aiohttp_get(URL)
datetime = response.headers.get('Date')
# 这里增加的asyncio.sleep是为了模拟每个请求有一定延迟返回
await asyncio.sleep(sleepy_time)
response.close()
return 'coroutine {}: {}, took: {:.2f} seconds'.format(
pid, datetime, time.time() - start)
async def main():
start = time.time()
futures = [fetch_async(i) for i in range(1, MAX_CLIENTS + 1)]
done, pending = await asyncio.wait(
futures, return_when=FIRST_COMPLETED,timeout=0.01
)
print(done)
for future in done:
print(future.result())
asyncio.run(main())

我这里把超时时间设置的非常小了是0.01,导致最后我打印done结果的时候其实三个任务没有一任务是被完成的:

fetch coroutine 2 started, sleeping for 4 seconds
fetch coroutine 3 started, sleeping for 3 seconds
fetch coroutine 1 started, sleeping for 4 seconds
set()

关于asyncio知识一的更多相关文章

  1. 关于asyncio知识(四)

    一.使用 asyncio 总结 最近在公司的一些项目中开始慢慢使用python 的asyncio, 使用的过程中也是各种踩坑,遇到的问题也不少,其中有一次是内存的问题,自己也整理了遇到的问题以及解决方 ...

  2. 关于asyncio知识(二)

    一.asyncio之—-入门初探 通过上一篇关于asyncio的整体介绍,看过之后基本对asyncio就有一个基本认识,如果是感兴趣的小伙伴相信也会尝试写一些小代码尝试用了,那么这篇文章会通过一个简单 ...

  3. 关于asyncio知识(一)

    一.介绍 asyncio 是python3.4 引入的一个新的并发模块,主要通过使用coroutines 和 futures 来让我们更容易的去实现异步的功能,并且几乎和写同步代码一样的写代码,还没有 ...

  4. 读asyncio模块源码时的知识补漏

    硬着头皮看了一周的asyncio模块代码,了解了大概的执行流程,引用太多,成尤其是对象间函数的引用. 光是这么一段简单的代码: # coding: utf8 import asyncio import ...

  5. Python标准模块--asyncio

    1 模块简介 asyncio模块作为一个临时的库,在Python 3.4版本中加入.这意味着,asyncio模块可能做不到向后兼容甚至在后续的Python版本中被删除.根据Python官方文档,asy ...

  6. 我实在不懂Python的Asyncio

    原语 事件循环(Event Loop) Awaitables和Coroutines Coroutine Wrappers Awaitables and Futures Tasks Handles Ex ...

  7. 爬虫高性能 asyncio库 twisted库 tornado库

    一 背景知识 爬虫的本质就是一个socket客户端与服务端的通信过程,如果我们有多个url待爬取,只用一个线程且采用串行的方式执行,那只能等待爬取一个结束后才能继续下一个,效率会非常低. 需要强调的是 ...

  8. python基础知识1---python相关介绍

    阅读目录 一 编程与编程语言 二 编程语言分类 三 主流编程语言介绍 四 python介绍 五 安装python解释器 六 第一个python程序 七 变量 八 用户与程序交互 九 基本数据类型 十 ...

  9. 深入Asyncio(三)Asyncio初体验

    Asyncio初体验 Asyncio在Python中提供的API很复杂,其旨在替不同群体的人解决不同的问题,也正是由于这个原因,所以很难区分重点. 可以根据asyncio在Python中的特性,将其划 ...

随机推荐

  1. Git使用指南(上)

    1 Git简介 学习一门技术老师更加倾向于看官网的. 度娘看完了,官网看完了,大家还是很懵逼 学生成绩管理系统 登录模块   3.2 登录模块进一步完善    缺一个验证码的功能    3.3 登录模 ...

  2. Doris开发手记1:解决蛋疼的MySQL 8.0连接问题

    笔者作为Apache Doris的开发者,平时感觉相关Doris的文章写的很少.主要是很多时候不知道应该去记录一些怎么样的问题,感觉写的不好就会很慌张.新的一年,希望记录自己在Doris开发过程之中所 ...

  3. TypeScript 3.7 RC & Nullish Coalescing

    TypeScript 3.7 RC & Nullish Coalescing null, undefined default value https://devblogs.microsoft. ...

  4. dart 匹配基本map

    var map_start = RegExp(r'^\s*\{\s*'); var map_end = RegExp(r'^\}\s*(,)?\s*'); var hasComma = true; M ...

  5. Masterboxan INC金融:在区块链技术基础上推动业务模式的变革创新

    10月初,2020年国际区块链技术与应用大会在硅谷开幕,全球内外区块链技术项目团队.行业领导.专家等共聚一堂,围绕区块链技术与应用展开讨论交流.美国Masterboxan INC万事达资产管理有限公司 ...

  6. 聊聊ASP.NET Core中的配置

    ​作为软件开发人员,我们当然喜欢一些可配置选项,尤其是当它允许我们改变应用程序的行为而无需修改或编译我们的应用程序时.无论你是使用新的还是旧的.NET时,可能希望利用json文件的配置.在这篇文章中, ...

  7. 敏捷史话(七):从程序员、作家到摇滚乐手——Andy Hunt的多面人生

    与其说 Andy Hunt 是敏捷宣言的创始人,不如说他是一名专业作家来得更为合适.他的<实用程序员><程序员修炼之道:从小工到专家><编程 Ruby:实用程序员指南&g ...

  8. 死磕Spring之IoC篇 - BeanDefinition 的加载阶段(XML 文件)

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  9. TERSUS无代码开发(笔记04)-CSS样式设置

    CSS样式设置 1.常用显示样式 大小尺寸 说明  间距边距 说明  各类颜色 说明  width 宽 margin 外边距         color  颜色        height 高 pad ...

  10. 3分钟学会如何上手supervisor看门狗

    软硬件环境 centos7.6.1810 64bit cat /etc/redhat-release #查看系统版本 supervisor 3.4.0 python 2.7.5 supervisor简 ...