一、asyncio
asyncio 不是多进程, 也不是多线程, 单单是一个线程, 但是是在 Python 的功能间切换着执行. 切换的点用 await 来标记, 能够异步的功能用 async 标记.
asyncio里面主要有4个需要关注的基本概念
Eventloop
Eventloop可以说是asyncio应用的核心,是中央总控。Eventloop实例提供了注册、取消和执行任务和回调的方法。
把一些异步函数(就是任务,Task,一会就会说到)注册到这个事件循环上,事件循环会循环执行这些函数(但同时只能执行一个),当执行到某个函数时,如果它正在等待I/O返回,事件循环会暂停它的执行去执行其他的函数;当某个函数完成I/O后会恢复,下次循环到它的时候继续执行。因此,这些异步函数可以协同(Cooperative)运行:这就是事件循环的目标
Coroutine
协程(Coroutine)本质上是一个函数,特点是在代码块中可以将执行权交给其他协程
Future
接着说Future,它代表了一个「未来」对象,异步操作结束后会把最终结果设置到这个Future对象上。Future是对协程的封装,不过日常开发基本是不需要直接用这个底层Future类的
Task
1. 用法
import asyncio
async def a():
print('Suspending a')
await asyncio.sleep(0)
print('Resuming a')
async def b():
print('In b')
async def main():
await asyncio.gather(a(), b())
if __name__ == '__main__':
asyncio.run(main())
"""
Start job 1
Start job 2
Job 1 takes 1 s
Job 2 takes 2 s
Async total time : 2.001495838165283
"""
这里面有4个重要关键点:
- 协程要用async def声明,Python 3.5时的装饰器写法已经过时,我就不列出来了。
- asyncio.gather用来并发运行任务,在这里表示协同的执行a和b2个协程
- 在协程a中,有一句await asyncio.sleep(0),await表示调用协程,sleep 0并不会真的sleep(因为时间为0),但是却可以把控制权交出去了。
- asyncio.run是Python 3.7新加的接口,要不然你得这么写:
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
多种使用方式
async def a():
print('Suspending a')
await asyncio.sleep(3)
print('Resuming a')
async def b():
print('Suspending b')
await asyncio.sleep(1)
print('Resuming b')
async def c1():
await asyncio.gather(a(), b())
async def c2():
await asyncio.wait([a(), b()])
async def c3():
task1 = asyncio.create_task(a())
task2 = asyncio.create_task(b())
await task1
await task2
async def c4():
task = asyncio.create_task(b())
await a()
await task
async def c5():
task = asyncio.ensure_future(b())
await a()
await task
async def c6():
loop = asyncio.get_event_loop()
task = loop.create_task(b())
await a()
await task
def show_perf(func):
print('*' * 20)
start = time.perf_counter()
asyncio.run(func())
print(f'{func.__name__} Cost: {time.perf_counter() - start}')
结果都是3秒。
asyncio.create_task相当于把协程封装成Task, 是Python 3.7新增的高阶API
2. asyncio.gather 与 asyncio.wait
差异
asyncio.wait : 写法 await asyncio.wait(task_list)
返回两个值:done 和 pending,done 为已完成的协程 Task,pending 为超时未完成的协程 Task,需通过 future.result 调用 Task 的 result;
asyncio.gather : 写法 await asyncio.gather(*task_list)
返回的是所有已完成 Task 的 result,gather的意思是「搜集,也就是能够收集协程的结果 , 它会按输入协程的顺序保存的对应协程的执行结果;
In : done, pending = await asyncio.wait([a(), b()])
Suspending b
Suspending a
Resuming b
Resuming a
In : done
Out:
{<Task finished coro=<a() done, defined at <ipython-input-5-5ee142734d16>:1> result='A'>,
<Task finished coro=<b() done, defined at <ipython-input-5-5ee142734d16>:8> result='B'>}
In : pending
Out: set()
In : task = list(done)[0]
In : task
Out: <Task finished coro=<b() done, defined at <ipython-input-5-5ee142734d16>:8> result='B'>
In : task.result()
Out: 'B'
asyncio.wait支持一个接收参数return_when,在默认情况下,asyncio.wait会等待全部任务完成(return_when=‘ALL_COMPLETED’),它还支持FIRST_COMPLETED(第一个协程完成就返回)和FIRST_EXCEPTION(出现第一个异常就返回)
async def a():
print('Suspending a')
await asyncio.sleep(3)
print('Resuming a')
return 'A'
async def b():
print('Suspending b')
await asyncio.sleep(1)
print('Resuming b')
return 'B'
done, pending = await asyncio.wait([a(), b()], return_when=asyncio.tasks.FIRST_COMPLETED)
Suspending a
Suspending b
Resuming b
Resuming b
In : done
Out: {<Task finished coro=<b() done, defined at <ipython-input-5-5ee142734d16>:8> result='B'>}
In : pending
Out: {<Task pending coro=<a() running at <ipython-input-5-5ee142734d16>:3> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x108065e58>()]>>}
这次只有协程b完成了,协程a还是pending状态。
在大部分情况下,用asyncio.gather是足够的,如果你有特殊需求,可以选择asyncio.wait,举2个例子:
- 需要拿到封装好的Task,以便取消或者添加成功回调等
- 业务上需要FIRST_COMPLETED/FIRST_EXCEPTION即返回的
二、aiohttp
aiohttp 是一个Python 提供异步HTTP 客户端/服务端编程, 基于asyncio的异步库, 它既提供了服务端 , 又提供了客户端. 客户端就是用来发起请求 , 类似于 requests 来发起一个 HTTP 请求然后获得响应 , 但 requests 发起的是同步的网络请求, 而 aiohttp 则发起的是异步的。
1. 用法
import aiohttp
timeout = aiohttp.ClientTimeout(total=60)
cookies = {'cookies_are': 'working'}
headers = {'User-Agent': 'your agent'}
async def aoi_main():
async with aiohttp.ClientSession(headers=headers) as sess: # 自定义headers
# async with aiohttp.ClientSession(timeout=timeout) as sess: # 设置请求超时
# async with aiohttp.ClientSession(cookies=cookies) as sess: # 自定义cookie
async with sess.get('http://httpbin.org/cookies') as resp: # SSL验证警告问题,ssl=False
assert resp.status == 200
print(resp.status)
print(await resp.text())
scrape_index_tasks = [asyncio.ensure_future(aoi_main()) for _ in range(300)]
loop = asyncio.get_event_loop()
tasks = asyncio.gather(*scrape_index_tasks)
loop.run_until_complete(tasks)
小知识点: assert用于测试一个条件是否满足, 不满足时会抛出 AssertionError错误信息
加代理
# 第一种
async with aiohttp.ClientSession() as session:
proxy_auth = aiohttp.BasicAuth('user', 'pass')
async with session.get("http://python.org", proxy="http://proxy.com", proxy_auth=proxy_auth) as resp:
print(resp.status)
# 第二种
session.get("http://python.org", proxy="http://user:pass@some.proxy.com")