python 异步 asyncio

版权声明: https://blog.csdn.net/dashoumeixi/article/details/81001681

异步介绍就不说了.

基础:python协程   python 生成器进阶

异步基于事件循环  事件循环基础

简单来说就是单线程并发,需要注意的是一个协程只运行在事件循环中

核心思想: yield from / await .交出当前函数的控制权(中断),让事件循环执行下个任务.看到一本书上说是"穷人的线程". 需要注意在协程中不再使用原始的yield

基本流程:

1.定义一个协程 (async def 或者 @asyncio.coroutine 装饰的函数)

2.调用上述函数,获取一个协程对象

2.1

(这步可作可不作) 通过asyncio.ensure_future 或 asyncio.async 函数调度协程(这部意味着要开始执行了) ,返回了一个Task对象

Task对象是Future对象的子类

3.获取一个事件循环

4.等待事件循环调度协程

    *后面的例子着重说明了一下as_completed ,附加了源码.  先说明一下:

    1. as_completed 每此迭代返回一个协程,

    2.这个协程内部从Queue从取出先完成的Future对象

    3.然后我们再 await coroutine

5.最后说明一下事件循环. 可以动态的增加协程到事件循环中. 而不是在一开始就确定所有需要协程. 

5.1. asyncio.get_event_loop() 默认情况下,这个事件循环是主线程的

下面例子中 asyncio.ensure_future/async都可以换成asyncio.run_coroutine_threadsafe :

"""
 第一个例子
 没什么用.
 注意: 协程 与 生成器 的用法是一样的. 需要调用之后才产生对象. 
"""
@asyncio.coroutine          #装饰一个普通函数. 当成协程
def func():
    print('hi')
lp = asyncio.get_event_loop() #获取事件循环
lp.run_until_complete(func())      #仍进事件循环里.注意,func() 而不是func. 需要调用之后才是协程对象.
"""
 用async def (新语法) 定义一个函数,同时返回值
 asyncio.sleep 模拟IO阻塞情况 ; await 相当于 yield from.
 await 或者 yield from 交出函数控制权(中断),让事件循环执行下个任务 ,一边等待后面的协程完成
"""
async def func(i):
    print('start')
    await asyncio.sleep(i)#交出控制权
    print('done')
    return i
co = func(2)    #产生协程对象
print(co)
lp = asyncio.get_event_loop()   #获取事件循环
task = asyncio.async(co)        #开始调度
lp.run_until_complete(task)     #等待完成
print(task.result())            #获取结果
"""
 添加一个回调:add_done_callback
"""
async def func(i):
    print('start')
    await asyncio.sleep(i)
    return i
def call_back(v):
    print('callback , arg:',v,'result:',v.result())
co = func(2)    #产生协程对象
lp = asyncio.get_event_loop()   #获取事件循环
task = asyncio.async(co)        #开始调度
task.add_done_callback(call_back) #增加回调
lp.run_until_complete(task)     #等待
print(task.result())            #获取结果

一个事件循环中执行多个task , 并发执行:

1.wait , gather  2个函数都是用于获取结果的.

2.两种用法 .

  第一种:           result = asyncio.run_until_completed(asyncio.wait/gather) 执行所有完成之后获取

  第二种在链中: result = await asyncio.wait/gather      在一个协程内获取结果

3. as_completed 与并发包concurrent 中的行为类似, 哪个任务先完成哪个先返回 , 内部实现是yield from Queue.get()

4. 嵌套: await / yield from 后跟协程. 为什么要嵌套? 比如你在协程中读写一个文件, 总不见得写一大堆代码在一个函数里吧.

    那么可以把读写一个文件封装在另一个协程中, 然后 yield from 协程.如果想获取结果那么 :

    yield from asyncio.wait/gather(协程s)

见下面各例子:

"""
    并发.执行多个任务.
    调度一个Task对象列表
    调用asyncio.wait 或者 asyncio.gather 获取结果
"""
async def func(i):
    print('start')
    await asyncio.sleep(i) #交出控制权,事件循环执行下个任务,同时等待完成
    return i

tasks = [asyncio.async(func(i)) for i in range(3)]  #开始调度一个Task对象列表
lp = asyncio.get_event_loop()
lp.run_until_complete(asyncio.wait(tasks))
for task in tasks:
    print(task.result())
"""
    通过await 或者 yield from 形成1个链, 后面跟其他协程. 形成一个链的目的很简单,
当前协程需要这个结果才能继续执行下去.
就跟普通函数调用其他函数获取结果一样
"""
async def func(i):
    print('start')
    await asyncio.sleep(i)
    return i

async def to_do():
    print('to_do start')
    tasks = []
    #开始调度3个协程对象
    for i in range(3):
        tasks.append(asyncio.ensure_future(func(i))) 
    #在协程内等待结果. 通过await 来交出控制权, 同时等待tasks完成
    task_done,task_pending = await asyncio.wait(tasks)
    print('to_do get result')
    #获取已经完成的任务
    for task in task_done:
        print('task_done:', task.result())
    #未完成的
    for task in task_pending:
        print('pending:',task)
lp = asyncio.get_event_loop()       #获取事件循环
lp.run_until_complete(to_do())      #把协程对象放进去
lp.close()                          #关闭事件循环

as_completed:这个函数的行为比较好玩 ,返回一个迭代器,每次迭代一个协程.

简单说一下,内部有一个Queue(queue.Queue 线程安全) , 先完成的先入队.

as_completed迭代的协程源码是 :  注意 yield from 后面可以跟 iterable

#简化版代码
f = yield from done.get() # done 是 Queue
return f.result()
async def func(x):
    # print('\t\tstart ',x)
    await asyncio.sleep(5)
    # print('\t\tdone ', x)
    return x
async def to_do():
    #在协程内调度2个协程
    tasks = [asyncio.ensure_future(func(i)) for i in range(2)]
    #使用as_completed:先完成,先返回.
    #每次迭代返回一个协程.
    #这个协程:_wait_for_one,内部从队列中产出一个最先完成的Future对象
    for coroutine in asyncio.as_completed(tasks):
        result =  await coroutine        #等待协程,并返回先完成的协程
        print('result :',result)
    print('all done')
lp = asyncio.get_event_loop()
lp.set_debug(True)
lp.run_until_complete(to_do())          #调度协程

下面关于事件循环的, 事件循环中维护了一个队列(FIFO, Queue) , 通过另一种方式来调用:

"""
 事件循环中维护了一个FIFO队列
 通过call_soon 通知事件循环来调度一个函数.
"""
def func(x):
    print('x:', x, ',start time:', time.ctime())
    time.sleep(x)
    print('func invoked:',x)

loop = asyncio.get_event_loop()
loop.call_soon(func,1)         #调度一个函数
loop.call_soon(func,2)
loop.call_soon(func,3)
loop.run_forever()                #阻塞

可以看到以上操作是同步的. 

接下来把通过asyncio.run_coroutine_threadsafe 函数可以把上述函数调度变成异步执行:

"""
    1.首先会调用asyncio.run_coroutine_threadsafe 这个函数.
    2.之前的普通函数修改成协程对象
"""
async def func(x):
    print('x:',x ,',start time:',time.ctime())
    await asyncio.sleep(x)
    print('func invoked:',x, ',now:',time.ctime())

loop = asyncio.get_event_loop()
co1 = func(1)
co2 = func(2)
co3 = func(3)
asyncio.run_coroutine_threadsafe(co1,loop) #调度
asyncio.run_coroutine_threadsafe(co2,loop)
asyncio.run_coroutine_threadsafe(co3,loop)
loop.run_forever()                #阻塞

上面2个例子只是告诉你2件事情.

1.run_coroutine_threadsafe(异步线程安全)  和call_soon (同步).

2.run_coroutine_threadsafe 这个函数 对应 ensure_future(只能作用于同一线程中). 

可以在一个子线程中运行一个事件循环,然后在主线程中动态的

添加协程,这样既不阻塞主线程执行其他任务,子线程也可以异步的执行协程.

需要注意的是: 默认情况下获取的event_loop 是主线程的. 所以要在子线程中使用event_loop 需要new_event_loop .如果

在子线程中直接获取event_loop 会抛异常 .源代码中的判断:

isinstance(threading.current_thread(), threading._MainThread)

例子:

import asyncio,time,os,sys,queue,threading
"""
    call_soon , call_soon_threadsafe 是同步的
    asyncio.run_coroutine_threadsafe(coro, loop) -> 对应 asyncio.ensure_future
    事件循环中,异步执行.
"""
#在子线程中执行一个事件循环 , 注意需要一个新的事件循环
def thread_loop(loop:asyncio.AbstractEventLoop):
    print('线程开启 tid:',threading.currentThread().ident)
    asyncio.set_event_loop(loop)                        #设置一个新的事件循环
    loop.run_forever()
async def func(x,q):
    print('func :', x , ',time:',time.ctime() , ',tid:',threading.currentThread().ident)
    await asyncio.sleep(x)
    q.put(x)
q = queue.Queue()
lp = asyncio.new_event_loop()                   #新建一个事件循环 , 如果使用默认的,则不能放入子线程
t = threading.Thread(target=thread_loop,args=(lp,))
t.start()
co1 = func(2,q)    #2个协程
co2 = func(3,q)
asyncio.run_coroutine_threadsafe(co1,lp)        #开始调度在子线程中的事件循环
asyncio.run_coroutine_threadsafe(co2,lp)
print('开始事件:',time.ctime())
while 1:
    x = q.get()
    print('main :',x,',time:',time.ctime())

猜你喜欢

转载自blog.csdn.net/dashoumeixi/article/details/81001681