# 只要实现过__await__魔法函数的,都叫做awaitable对象,就可以进行await
# 而async定义过的函数(也就是协程),都是实现过__await__的
'''from collections import Awaitable'''
# 使用协程装饰器,也同样能够是装饰的函数实现__await__魔法函数的
'''
import types
@types.coroutine
'''
# 如果使用yield和yield from将生成器设计为协程,会变得非常混乱,因为生成器和协程的功能会混乱
# python为了将语义更加明确,就引入了async和await关键词,用于定义原生的协程
# 并且为了功能更清晰,await只能在async前置的函数里,在async函数里不能出现yield和yield from
'''
async def downloader(url):
return "bobby"
async def downloader_url(url):
# dosomething
# 将控制权交出去,并等待结果返回
# await相当于yield from,但yield from后边跟的是生成器对象,而await后跟的是awaitable对象
html = await downloader(url)
return html
if __name__ == "__main__":
coro = downloader_url("http://www.imooc.com")
# 使用async和await原生协程时,不能使用next对协程进行调用,只能使用send,启动协程
# next(coro)
coro.send(None)
'''
# 生成器是可以暂停的函数,它是有状态的
# 协程:函数可接收、可发送数据
# 1、返回值给调用方,2、调用方通过send方式返回值给gen
'''
import inspect
def gen_func():
# 1、返回值给调用方,2、调用方通过send方式返回值给gen
value = yield 1
return "bobby"
if __name__ == "__main__":
gen = gen_func()
print(inspect.getgeneratorstate(gen)) # GEN_CREATED
next(gen)
print(inspect.getgeneratorstate(gen)) # GEN_SUSPENDED
try:
next(gen)
except StopIteration:
pass
print(inspect.getgeneratorstate(gen)) # GEN_CLOSED
'''
# 1、用同步的方式编写异步代码,在适当的时候暂停函数,并在适当的时候启动函数
# 通过协程提高并发,并以同步的方式做
# 传统的同步编写模式:如果子函数抛出异常,会向上抛
# 协程可以通过yield\yield from模式,对数据进行传输
# 而对于耗时的IO操作,先注册到selector中,再直接yield from出去,【不能在程序中写time.sleep,这很耗时】
# 以下案例不可运行,只用做讲解同步写协程
'''
import inspect
import socket
def get_socket_data():
yield "bobby"
def downloader(url):
# 建立socket连接
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.setblocking(False)
try:
client.connect((host,80))
except BlockingIOError as e:
pass
selector.register(client.fileno(),EVENT_WRITE,self.connected)
# 会暂停在这,直到事件循环的IO状态有改变,就会唤醒对应的协程
source = yield from get_socket_data()
html_data = data.split("\r\n\r\n")[1]
print(html_data)
def download_html(html):
# 会暂停在这,直到事件循环的IO状态有改变,就会唤醒对应的协程
html = yield from downloader()
if __name__== "__main__":
# 协程的调度依然是事件循环+协程模式,协程是单线程模式
pass
'''
# asyncio并发编程,解决异步IO并发编程的一整套解决方案,与同步编码模式有很大的区别,python最具野心的模块,web服务器,或高并发的爬虫
# tornado\gevent\twisted(scrapy\django channels)
# tornado实现了web服务器,可以直接部署,但一般会用nginx+tornado
# 但django\flask本身只是框架,但不是web服务器,简单实现socket,但不做实际部署用,一般搭配(uwsgi,gunicorn+nginx
# asyncio的基本功能:事件循环
# asyncio可理解为一个框架
# 包含各种特定系统实现的模块化事件循环:poll、epoll
# 传输和协议抽象
# TCP\UDP\SSL\子进程、延时调用等
# 模仿futures模块但适用于事件循环使用的Future类
# 基于yield from的协议和任务,可以让你用顺序的方式编写并发代码
# 必须使用一个将产生阻塞IO的调用时,有接口可以把这个事件转移到线程池
# 模仿threading模块中的同步原语,可以用在单线程内的协程之间
# 事件循环+回调(驱动生成器或是驱动协程)+epoll(IO多路复用)
# 使用asyncio,但在协程里,绝对不能使用同步阻塞的方式,例如time.sleep。
# 但可以使用asyncio自带的sleep
# asyncio是无法与request结合的,需要另外找替代request的
import time
async def get_html(url):
print(f"start get {url}")
# 使用同步阻塞的io,例如time.sleep(2)是不报错的,但是如果用了,也会阻塞其他协程的运行
# 使用asyncio后,要用await等待它。await后边跟的,必须是awaitable的对象
await asyncio.sleep(2)
print(f"end get {url}")
# 添加callback,回调函数
def callback(future,url):
print("send email to bobby")
if __name__ == "__main__":
start_time = time.time()
# 直接在asyncio的事件循环,可以将asyncio当做协程池
loop = asyncio.get_event_loop()
# 相当于多线程中的join方法,让此协程进入阻塞状态。等执行完毕了再往下执行
# 只执行单个任务,或是让单个协程进入阻塞
'''
loop.run_until_complete(get_html("http://www.imooc.com"))
print(time.time()-start_time)
'''
# loop.create_task()和asyncio.ensure_future()其实是等效的,用于获取协程的返回值
# loop.create_task()返回的是task类型,task是future的子类
# task = loop.create_task()
# asyncio是如何将future与loop相关联呢?实际在asyncio.ensure_future的后台,会自动使用loop。因为一个线程只有一个loop
'''
get_future = asyncio.ensure_future(get_html("http:www.imooc.com"))
# run_until_complete可接收协程类型,也可以接收future类型
loop.run_until_complete(get_future)
print(get_future.result())
print(time.time()-start_time) # 时间也是2秒左右,并发性非常高!!!
'''
'''
# 使用loop.create_task()获取协程的返回值
task = loop.create_task(get_html("http:\\www.imooc.com"))
# callback函数是在执行完协程任务后不着急返回协程的return,等再执行callback任务结束后,最后才执行协程的return,将协程的返回值放入task的result()中
# 但add_done_callback只能接收函数名,不能接收参数,所以一旦callback函数设置了形参,则需要functools库中的partial
task.add_done_callback(callback)
'''
# 如果callback函数有参数,用偏函数partial,如下方法:
'''
from functools import partial
task.add_done_callback(partial(callback,"http://www.imooc.com"))
'''
'''
loop.run_until_complete(task)
print(task.result())
'''
# wait当需要一次性提交多个协程时,可用wait。wait本身是个协程(使用@coroutine装饰器),类似于多线程中的wait
# 获取多个协程对象,放入列表
'''
tasks = [get_html(f"http:www.imooc.com{i}") for i in range(10)]
# 并让多个协程进入阻塞,可以使用asyncio.wait(可迭代对象)
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start_time) # 时间也是2秒左右,并发性非常高!!!
'''
# gather也可用于实现多个任务提交,与wait很相似
# gather和wait的区别,gather更加high-level抽象
'''
group1 = [get_html("http://projectsedu.com") for i in range(2)]
group2 = [get_html("http://www.imooc.com") for i in range(2)]
# *group1,这里是为了将列表中的每个元素(即协程)解析为参数,并放入gather中
group1 = asyncio.gather(*group1)
group2 = asyncio.gather(*group2)
# 可以将任务分组,并且成批取消任务
'''
'''group1.cancel()'''
'''
loop.run_until_complete(asyncio.gather(group1,group2))
print(time.time()-start_time)
'''
# run_until_complete:在运行完指定协程后,停止循环
# run_forever:无限循环
'''
import asyncio
loop = asyncio.get_event_loop()
loop.run_forever()
loop.run_until_complete()
'''
# 1、loop会被放到future中(而源码中也将future放到loop中,逻辑是有些混乱的)
# 2、取消future(task):采集数据入库时,若某个数据入库失败,可取消整个入库任务
'''
import asyncio
import time
async def get_html(sleep_times):
print("waiting")
await asyncio.sleep(sleep_times)
print("done after{}s".format(sleep_times))
if __name__=="__main__":
task1 = get_html(2)
task2 = get_html(3)
task3 = get_html(3)
tasks = [task1,task2,task3]
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(asyncio.wait(tasks))
except KeyboardInterrupt as e: # 按下ctl+c
# 没有传递任何loop,而在all_tasks的内部会去搜索loop!然后获取loop中的所有task
all_tasks = asyncio.Task.all_tasks()
for task in all_tasks:
print("cancel task")
pirnt(task.cancel()) # 有些正在运行的task无法取消
# loop的stop仅仅是更改了状态位stopping为True
loop.stop()
# stop之后一定要run_forever(),否则报错
loop.run_forever()
finally:
# close会将_ready队列、_schedule队列清空、将executor线程池都给关闭掉
loop.close()
'''
# 协程里调用子协程,有助于理解task内部的调度(类比与yield from 、yield里的通道建立)
# 当获取到事件循环loop,然后loop调用run_until_complete时就会创建Task,
# 此时Task就是调用方,而run_until_complete运行的是print_sum,那么print_sum是委托函数(通道),在委托函数中await compute(x,y),compute就成了子协程(类似子生成器)
# 这时Task和compute之间建立了一个通道,运行的过程如下:
# 进入print_sum后,会立即进入第一行代码await compute,print_sum会被await阻塞,print_sum会进入暂停状态,然后进入compute协程
# 进入compute后,先print,然后await后,compute这个协程就会暂停,会从compute直接返回调用方Task
# 执行asyncio.sleep(2),暂停2秒后,Task又会唤醒compute,compute会往下执行return1+2,return 1+2其实会抛出raise StopIteration(3)
# print_sum就会接收到StopIteration(3)里的3,并赋值给result。往下执行print("sum is",result),打印完之后会抛出raise StopIeration给Task
# 然后Task最后Stop之后,然后loop就变为stopped。
'''
import asyncio
async def compute(x,y):
print("computing")
# asyncio.sleep(2)并不是协程,而是会抛出raise StopIteration给调用方,
await asyncio.sleep(2)
return x+y
async def print_sum(x,y):
# await相当于yield from
result = await compute(x,y)
print("sum is ",result)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
# 当获取事件循环后,
loop.run_until_complete(print_sum(1,2))
'''
# call_soon\call_at\call_later\call_soon_threadsave
def callback(sleep_times):
print("sleep{}success".format(sleep_times))
def stoploop(loop):
loop.stop()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
# call_soon是即刻执行,在队列里等待到下一个循环的时候,会立马执行
'''
loop.call_soon(callback,2)
loop.call_soon(stoploop,loop)
'''
# call_later(秒数,回调函数,参数),在几秒后执行回调函数,当有多个call_later时,会根据延时调用的时间来确定执行的先后顺序
'''
loop.call_later(2,callback,3)
loop.call_later(1, callback, 3)
loop.call_later(3, callback, 3)
loop.call_soon(callback,4) # 当有call_soon的时候,先执行call_soon再去执行call_later
'''
# call_at(时刻,callback,参数)
now = loop.time()
loop.call_at(now+1,callback,now+1)
loop.call_at(now+2,callback,now+2)
loop.call_at(now+3,callback,now+3)
loop.call_soon(callback,4)
loop.run_forever()
loo.run_until_complete()
基础使用
# asyncio是python的原生协程
import asyncio
import time
# 没有事件循环的协程async
'''
async def downloader(url):
return "bobby"
async def downloader_url(url):
# dosomething
# 将控制权交出去,并等待结果返回
# await相当于yield from,但yield from后边跟的是生成器对象,而await后跟的是awaitable对象
html = await downloader(url)
print(html)
return html
if __name__ == "__main__":
coro = downloader_url("http://www.imooc.com")
# 使用async和await原生协程时,不能使用next对协程进行调用,只能使用send,启动协程
# next(coro)
coro.send(None)
'''
# async设置的函数,调用时只会返回协程对象,但是不会执行函数的,只有注册到事件循环中,才能执行协程函数
# await用于挂起阻塞的异步调用接口
# 数据相关性的async需要在服务器端进行数据存储计算!
# 但要设置事件循环,建立数据通道,并且需要返回数据(计算数据个数count、数据总和sum、数据平均值average),
# 如果是要累计数据,则需要服务器做数据存储,再将多次传递的数据进行计算后返回,因为任务之间不是独立的,而是有数据相关性的
sum = 0
count = 0
average = 0
async def calculate(value):
global sum,count,average
sum += value
count += 1
average = sum/count
return value,sum,count,average
async def sales_sum(value):
result = await calculate(value)
print(result)
sum_1,count_1,average_1 = 0,0,0
def calculate_old(value):
global sum_1,count_1,average_1
sum_1 += value
count_1 += 1
average_1 = sum_1/count_1
return value,sum_1,count_1,average_1
if __name__=="__main__":
start_time = time.time()
loop = asyncio.get_event_loop()
sales = [1,2,3,4,5,6]
tasks = [sales_sum(i) for i in sales]
# 当获取事件循环后,
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
print(time.time()-start_time)
start_time = time.time()
for i in sales:
s = calculate_old(i)
print(s)
print(time.time()-start_time)