13-协程 asyncio

# 只要实现过__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)

你可能感兴趣的:(python高级,python)