异步操作目的是使用一个主线程实现多个协程并发执行
定义一个函数,该函数是一个消费者的生成器,使用装饰器@asyncio.coroutine 把生成器变成协程,协程的程序是可以中断再执行的,异步操作需要在协程中中通过yield from完成;yield from语法可以让我们方便地调用另一个generator,生成器既可以是生产者也可以是消费者,当一个任务的程序执行到这里时,会中断程序,调用其他的生成器,yield返回值是需要时间的,但是主线程是不会等待这个任务结束的,而是会去event_loop中执行其他可执行的任务,等到yield from拿到值时,这时会继续执行中断的程序
异步操作是通过yield from 来实现的 当程序执行到该语句时,会由于避免等待而去执行其他任务,yield from 后面可以跟携程或者生成器
当哪个携程执行不了了, 线程会先执行其他的协程
import asyncio
import threading
# 把生成器变成协程
@asyncio.coroutine
def countdown(name, num):
# 打印当前的线程
print(threading.current_thread())
while num > 0:
# 打印任务的名称 和 名字 这一句是为了验证任务的并
发执行,任务1 执行到yield from会中断程序,线程会
执行任务2 任务2 依然打印出名字 之后 执行到 yield
from 中断程序, 执行任务3 ,这时就会把任务1、2、3
的名字同时打印出来,等到任务yield 拿到值时中断的程序
会接着往下执行
print((f'Countdown[{name}]: {num}'))
# 异步执行 - 非阻塞式 asyncio.sleep()也是一个
协程 异步执行靠的就是yield from 执行其他的协程,
执行其他协程时需要耗费时间,此时程序会终止到这里,
但是异步执行时,是不会阻塞在这里的, 主线程并未等待,
因此可以实现并发执行任务
这里yield得到的值是None,
如果换成是真正的I/O操作这里就可以得到返回值了
yield from asyncio.sleep(1)
# 同步执行 - 阻塞式 如果把休息时间写成这样的话, 程序执
行到这里会卡在这里,形成阻塞,也不会去执行其他可执行的任
务了
# time.sleep(1)
num -= 1
def main():
# 通过下面的event_loop实现多任务的并发执行
loop = asyncio.get_event_loop()
# 异步I/O - 虽然只有一个线程但是两个任务相互之间不阻塞
tasks = [
countdown('A', 10), countdown('B', 5)
]
loop.run_until_complete(asyncio.wait(tasks))
# 任务执行完了,关闭循环
loop.close()
if __name__ == '__main__':
main()
asyncio.sleep(1), 可以看做是一个耗时一秒的I/O操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。
如果把asyncio.sleep()换成真正的IO操作,则多个coroutine就可以由一个线程并发执行,具体操作看廖雪峰官网
async/await
用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。
为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读
请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:
把@asyncio.coroutine替换为async;
把yield from替换为await。
@asyncio.coroutine
def hello():
print("Hello world!")
r = yield from asyncio.sleep(1)
print("Hello again!")
新用法:
async def hello():
print("Hello world!")
r = await asyncio.sleep(1)
print("Hello again!")
实例示范:
import asyncio
import requests
import time
async def download(url):
print('Fetch:', url)
# 联网下载可能耗时, 异步取, 加上yield变成了协程
# time.sleep(10) 阻塞式的
# 程序执行到这里 中断3秒 上面的print并发执行
await asyncio.sleep(3)
resp = requests.get(url)
print(resp.status_code)
# 程序执行到这里中断5秒 上面的print并发执行
await asyncio.sleep(5)
print(url)
# print(resp.headers)
def main():
loop = asyncio.get_event_loop()
urls = [
'https://www.baidu.com',
'http://www.sohu.com',
'http://www.sina.com',
'https://www.taobao.com',
'http://www.qq.com'
]
# 启动下载任务
tasks = [download(url) for url in urls]
# 异步执行任务
loop.run_until_complete(asyncio.wait(tasks))
if __name__ == '__main__':
main()
程序运行结果如下图:
aiohttp
实例示范:
import asyncio
import aiohttp
async def download(url):
print('Fetch:', url)
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print(url, '--->', resp.status)
print(url, '--->', resp.cookies)
# \n\n意思是隔两个空行打印一次
# await 打印可能耗时, 中断异步执行,
# print('\n\n', await resp.text())
def main():
loop = asyncio.get_event_loop()
urls = [
'https://www.baidu.com',
'http://www.sohu.com',
'http://www.sina.com',
'https://www.taobao.com',
'http://www.qq.com'
]
tasks = [download(url) for url in urls]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
if __name__ == '__main__':
main()