python--asyncio模块

申明:本文章参考于https://www.jianshu.com/p/b5e347b3a17c

网络模型有很多种,为了实现高并发也有很多方案,多线程,多进程。无论多线程和多进程,IO的调度更多取决于系统,而协程的方式,调度来自用户,用户可以在函数中yield一个状态。使用协程可以实现高效的并发任务。Python的在3.4中引入了协程的概念,可是这个还是以生成器对象为基础,3.5则确定了协程的语法。下面将简单介绍asyncio的使用。实现协程的不仅仅是asyncio,tornado和gevent都实现了类似的功能。


event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。

coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。

task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。

future: 代表将来执行或没有执行的任务的结果。它和task上没有本质的区别

async/await 关键字:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。

1.定义一个协程
# 如何定义一个协程:只需要在定义的函数前加上async关键字即可
import time
import asyncio


# 协程的定义可以通过async关键字来定义,当然定义之后的对象无法直接运行,
# 类似于twisted,需要将其扔到事件循环当中
async def func(name):
    print(f'my name is {name}')

st_time = time.time()
coroutine = func('古明地盆')  # 创建一个协程
loop = asyncio.get_event_loop()  # 创建一个事件循环
loop.run_until_complete(coroutine)  # 将要执行的协程注册到事件循环中,并启动。

print('用时:',time.time()-st_time)

输出结果如下

my name is 古明地盆
用时: 0.0

2.定义协程的另一种方式

import time
import asyncio

# 可以使用async关键字,也可以给函数添加一个装饰器,依旧可以把函数包装成一个协程
@asyncio.coroutine
def func(name):
    print(f'my name is {name}')

st_time = time.time()
coroutine = func('古明地盆')
loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine)

print('用时:',time.time()-st_time)


# asyncio.coroutine函数源码
def coroutine(func):
    """Decorator to mark coroutines.

    If the coroutine is not yielded from before it is destroyed,
    an error message is logged.
    """
    if _inspect_iscoroutinefunction(func):
        # In Python 3.5 that's all we need to do for coroutines
        # defined with "async def".
        # Wrapping in CoroWrapper will happen via
        # 'sys.set_coroutine_wrapper' function.
        return func

    if inspect.isgeneratorfunction(func):
        coro = func
    else:
        @functools.wraps(func)
        def coro(*args, **kw):
            res = func(*args, **kw)
            if (base_futures.isfuture(res) or inspect.isgenerator(res) or
                isinstance(res, CoroWrapper)):
                res = yield from res
            elif _AwaitableABC is not None:
                # If 'func' returns an Awaitable (new in 3.5) we
                # want to run it.
                try:
                    await_meth = res.__await__
                except AttributeError:
                    pass
                else:
                    if isinstance(res, _AwaitableABC):
                        res = yield from await_meth()
            return res

    if not _DEBUG:
        if _types_coroutine is None:
            wrapper = coro
        else:
            wrapper = _types_coroutine(coro)
    else:
        @functools.wraps(func)
        def wrapper(*args, **kwds):
            w = CoroWrapper(coro(*args, **kwds), func=func)
            if w._source_traceback:
                del w._source_traceback[-1]
            # Python < 3.5 does not implement __qualname__
            # on generator objects, so we set it manually.
            # We use getattr as some callables (such as
            # functools.partial may lack __qualname__).
            w.__name__ = getattr(func, '__name__', None)
            w.__qualname__ = getattr(func, '__qualname__', None)
            return w

    wrapper._is_coroutine = _is_coroutine  # For iscoroutinefunction().
    return wrapper

输出结果如下

my name is 古明地盆
用时: 0.0

3.创建task任务

import time
import asyncio

# 协程对象并不能直接运行,需要的是task对象或future对象。
# 但在注册事件循环的时候,run_until_complete()函数会将协程包装成一个task对象
# task是future对象的子类,保存了协程运行后的状态,可以在未来的时候获取结果
# 所记不错的话,tornado4.0的时候也引入了future对象。future对象在tornado里面就类似于一个信使
# 穿梭于ioloop这个全局总调度器和协程之间

async def func(name):
    print(f'my name is {name}')

st_time = time.time()
coroutine = func('古明地盆')
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
print(task)
loop.run_until_complete(task)
print(task)
print('用时:',time.time()-st_time)

输出结果如下

<Task pending coro=<func() running at C:/Users/Administrator/Desktop/完美世界/python--asyncio/3创建task任务.py:10>>
my name is 古明地盆
<Task finished coro=<func() done, defined at C:/Users/Administrator/Desktop/完美世界/python--asyncio/3创建task任务.py:10> result=None>
用时: 0.0

  

为什么会输出上述结果,task在加入事件循环之前处于挂起状态,在加入事件循环执行完毕后,便处于了finished状态
asyncio.ensure_future()和loop.create_task(),都可以创建一个task,run_until_complete()接收的参数是一个future对象
当传入一个协程,内部会自动封装成一个task对象,task是future的子类.
print(isinstance(task,asyncio.Future))
# 打印结果为True

4.绑定回调

import time
import asyncio
# 绑定函数,在task执行完毕后可以获取执行的结果,回调的最后一个参数是future对象,通过该对象可以获取协程的返回值。
# 如果回调需要多个参数,可以使用python标准库functools下的partial偏函数导入

@asyncio.coroutine # 也可以用这种方式来定义一个协程
def func(name):
    print(f'my name is {name}')
    print('我要执行完毕了·····,下面我将指定一个白痴来接收我的返回值·····')
    return '多睡觉,少操心'

def callback(future):
    print('我是白痴,开始接收返回值·····')
    print('收到返回值,打印:',future.result())

coroutine = func('古明地盆')
loop = asyncio.get_event_loop()
st_time = time.time()

# 也可以使用future=asyncio.ensure_future(coroutine)
# 也可以不写,如果不写,内部会自动包装成一个task对象,但此时就没办法使用回调
task = loop.create_task(coroutine)
# 添加回调函数,当task任务执行完毕之后会自动执行
task.add_done_callback(callback)
# 将包装好的task对象扔到事件循环当中
loop.run_until_complete(task)
print('用时:',time.time()-st_time)

输出结果如下

my name is 古明地盆
我要执行完毕了·····,下面我将指定一个白痴来接收我的返回值·····
我是白痴,开始接收返回值·····
收到返回值,打印: 多睡觉,少操心
用时: 0.0

5.future与result

import asyncio
import time
# 回调编程一直是程序员的噩梦,包括我在内,程序员更喜欢用同步的方式编写异步代码,以避免回调的噩梦
# 回调中我们使用了future对象的result方法,在前面不绑定回调的栗子中,我们看到task有finished状态。在那个时候,可以直接读取task的result方法

@asyncio.coroutine
def func(name):
    print(f'my name is {name}')
    time.sleep(3)
    return '多睡觉少操心'


st_time = time.time()
coroutine = func('古明地盆')
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
loop.run_until_complete(task)

# 我们可以让程序睡三秒钟,发现在协程没有执行完毕的时候,task.result()处于阻塞状态,程序会卡在这里
# 在协程执行完毕,会获取到返回值并打印
print('task result',task.result())
print('用时:',time.time()-st_time)

输出结果如下

my name is 古明地盆
task result 多睡觉少操心
用时: 3.0127999782562256

6.阻塞与await

import time
import asyncio

# 使用async可以定义协程对象,使用await可以针对耗时的操作进行挂起,就想生成器里的yield一样,函数让出控制权。
# 协程遇到await,事件循环将会挂起该协程,执行别的协程,直到其他的协程也挂起或者执行完毕,再进行下一个协程的执行。

# 而一般耗时的主要是io操作,例如网络请求,文件读取等等,这些操作是不占用CPU的。我们可以使用asyncio.sleep()来模拟io操作
# 协程的目的也是使这些io操作异步化
# PS:所以这个模块才叫asyncio(异步io)呀~~~~~~~~

# 很重要的一点,之前我们说过讲一个函数变成协程有两种方式,其中一种是加上一个装饰器
# 由于这里使用了await关键,那么因此只能使用async关键字来定义一个协程。
# 至于为什么,是因为yield既可以作为生成器也可以当做协程。为了不引起歧义,python引入了async和await两个关键字
# 因此这两个关键字必须成对出现,并且yield不可以出现在async定义的协程当中
async def func(name):
    print(f'my name is {name}')
    await asyncio.sleep(3)
    return '多睡觉,少操心'

st_time = time.time()
coroutine = func('古明地盆')
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
loop.run_until_complete(task)

print('TASK RESULT,',task.result())
print('用时:',time.time()-st_time)

输出结果如下

my name is 古明地盆
(隔了三秒钟)
TASK RESULT, 多睡觉,少操心
用时: 3.0002057552337646

7.并发与并行

# 并发是指同一时间断有多个任务执行,但在同一时刻只有一个任务在实行
# 并行是指同一时刻有多个任务在执行

# asyncio实现并发,需要创建多个协程。每当任务阻塞的时候就await,然后其他协程继续工作。
# 创建多个协程的列表,将这些协程注册到事件循环中

import asyncio
import time

async def func(n):
    print(f'接下来等待{n}秒')
    await asyncio.sleep(n)
    return f'我等待了{n}秒'

st_time = time.time()
coroutine1 = func(1)
coroutine2 = func(2)
coroutine3 = func(4)

loop = asyncio.get_event_loop()
tasks = [
    loop.create_task(coroutine1),
    loop.create_task(coroutine2),
    loop.create_task(coroutine3)
]

# 也可以使用async.gather(*task),但是要将其拆成一堆task
loop.run_until_complete(asyncio.wait(tasks))

for task in tasks:
    print('task result',task.result())

print('用时:',time.time()-st_time)

输出结果如下

接下来等待1秒
接下来等待2秒
接下来等待4秒
task result 我等待了1秒
task result 我等待了2秒
task result 我等待了4秒
用时: 4.004607677459717
# 值得注意的是,程序并不是一下子就将结果返回,比如等待一秒钟的那个协程,并不是等了一秒钟就打印结果
# 而是等待所有协程都执行完毕之后再按照先后执行完毕的顺序一块打印

8.协程嵌套(1)

# 使用async定义协程,那么我们也可以在其内部封装更多的io操作,这样便实现了协程的嵌套
# 即在一个协程中await另一个协程,如此连接起来

import asyncio
import time


async def love_cat(t):
    print(f'等待{t}秒···')
    await asyncio.sleep(t)
    return f'等待了{t}秒'

async def main():
    coroutine1 = love_cat(1)
    coroutine2 = love_cat(2)
    coroutine3 = love_cat(4)
    tasks = [
        asyncio.ensure_future(coroutine1),
        asyncio.ensure_future(coroutine2),
        asyncio.ensure_future(coroutine3),
    ]
    # 如果没有协程的嵌套,那么我们直接可以将tasks扔进事件循环当中
    # 等待执行完毕,直接通过task.result() for task in tasks获取结果
    # 但此时出现了协程的嵌套,位于协程的内部,无法通过task.result() for task in tasks获取结果
    # 但注意,此时不要再尝试执行loop.run_until_complete(tasks)
    # 因为当外部的协程扔进事件循环当中,也可以执行内部的协程
    # 直接通过await asyncio.wait(tasks)即可,会有两个返回值,一个已完成,一个未完成
    dones,pending = await asyncio.wait(tasks)  # 会有两个返回值,一个是已经完成,一个是未完成
    # dones里面则包含了所有已经完成的task/future对象
    for future in dones:
        print('TASK RESULT',future.result())

st_time = time.time()
loop = asyncio.get_event_loop()
task = loop.create_task(main())
loop.run_until_complete(task)
print('用时:',time.time()-st_time)

输入结果如下

等待1秒···
等待2秒···
等待4秒···
TASK RESULT 等待了4秒
TASK RESULT 等待了1秒
TASK RESULT 等待了2秒
用时: 4.019207715988159

9.携程嵌套(2)

import asyncio
import time


async def love_cat(t):
    print(f'等待{t}秒···')
    await asyncio.sleep(t)
    return f'等待了{t}秒'

async def main():
    coroutine1 = love_cat(1)
    coroutine2 = love_cat(2)
    coroutine3 = love_cat(4)
    tasks = [
        asyncio.ensure_future(coroutine1),
        asyncio.ensure_future(coroutine2),
        asyncio.ensure_future(coroutine3),
    ]
    # 可以直接返回await,让最外层的run_until_complete返回main协程的结果
    return await asyncio.gather(*tasks)


st_time = time.time()
loop = asyncio.get_event_loop()
task = loop.create_task(main())
results = loop.run_until_complete(task)
for result in results:
    print('task result',result)
print('用时:',time.time()-st_time)

输出结果如下

等待1秒···
等待2秒···
等待4秒···
task result 等待了1秒
task result 等待了2秒
task result 等待了4秒
用时: 4.013409852981567

10.协程嵌套(3)

import asyncio
import time


async def love_cat(t):
    print(f'等待{t}秒···')
    await asyncio.sleep(t)
    return f'等待了{t}秒'

async def main():
    coroutine1 = love_cat(1)
    coroutine2 = love_cat(2)
    coroutine3 = love_cat(4)
    tasks = [
        asyncio.ensure_future(coroutine1),
        asyncio.ensure_future(coroutine2),
        asyncio.ensure_future(coroutine3),
    ]
    # 可以用asyncio.wait直接返回await,让最外层的run_until_complete返回main协程的结果
    return await asyncio.wait(tasks)


st_time = time.time()
loop = asyncio.get_event_loop()
task = loop.create_task(main())
dones,pending = loop.run_until_complete(task)
for task in dones:
    print('task result',task.result())
print('用时:',time.time()-st_time)

输出结果如下

等待1秒···
等待2秒···
等待4秒···
task result 等待了1秒
task result 等待了2秒
task result 等待了4秒
用时: 4.014608144760132

11.协程嵌套(4)

import asyncio
import time


async def love_cat(t):
    print(f'等待{t}秒···')
    await asyncio.sleep(t)
    return f'等待了{t}秒'

async def main():
    coroutine1 = love_cat(1)
    coroutine2 = love_cat(2)
    coroutine3 = love_cat(4)
    tasks = [
        asyncio.ensure_future(coroutine1),
        asyncio.ensure_future(coroutine2),
        asyncio.ensure_future(coroutine3),
    ]
    # 也可以使用asyncio.as_complete方法
    for task in asyncio.as_completed(tasks):
        result = await task
        print('task result',result)


st_time = time.time()
loop = asyncio.get_event_loop()
task = loop.create_task(main())
loop.run_until_complete(task)

输出结果如下

等待1秒···
等待2秒···
等待4秒···
task result 等待了1秒
task result 等待了2秒
task result 等待了4秒

# 这里打印返回值则不是一次性打印,而是谁先执行完,先返回谁。

12.取消协程(1)

# 上面提到的协程的用法,都是协程围绕着事件循环进行的操作。future有以下几种状态
# pending  running  done  cancelled
# 创建task的时候,此时还没有将其扔进循环队列,那么状态自然为pending(被挂起)
# 事件循环调用执行的时候自然是running
# 调用完毕自然就是done,此时可以通过result()方法获取返回值
# 如果需要停止事件循环那么需要把task取消

# 使用async定义协程,那么我们也可以在其内部封装更多的io操作,这样便实现了协程的嵌套
# 即在一个协程中await另一个协程,如此连接起来

import asyncio
import time

async def love_cat(t):
    print(f'等待{t}秒···')
    await asyncio.sleep(t)
    return f'等待了{t}秒'


coroutine1 = love_cat(1)
coroutine2 = love_cat(2)
coroutine3 = love_cat(4)
tasks = [
    asyncio.ensure_future(coroutine1),
    asyncio.ensure_future(coroutine2),
    asyncio.ensure_future(coroutine3),
]

st_time = time.time()
loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(asyncio.wait(tasks))
except KeyboardInterrupt as e:
    print(asyncio.Task.all_tasks())
    for task in asyncio.Task.all_tasks():
        print(task.cancel())
    loop.stop()
    loop.run_forever()
finally:
    loop.close()
print('用时:',time.time()-st_time)

# 启动事件循环之后,马上按Ctrl+C,会触发run_until_complete的执行异常KeyBoardInterrupt
# 然后通过循环asyncio.Task取消future

输出结果如下

等待1秒···
等待2秒···
等待4秒···
{<Task pending coro=<love_cat() running at 9协程停止.py:16> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000000002AB56A8>()]> cb=[_
wait.<locals>._on_completion() at C:\python36\lib\asyncio\tasks.py:380]>, <Task pending coro=<love_cat() running at 9协程停止.py:16> wait_for=<Future p
ending cb=[<TaskWakeupMethWrapper object at 0x0000000002B5B828>()]> cb=[_wait.<locals>._on_completion() at C:\python36\lib\asyncio\tasks.py:380]>, <Tas
k pending coro=<love_cat() running at 9协程停止.py:16> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000000002B5B888>()]> cb=[_wait.
<locals>._on_completion() at C:\python36\lib\asyncio\tasks.py:380]>, <Task pending coro=<wait() running at C:\python36\lib\asyncio\tasks.py:313> wait_f
or=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000000002B5B9A8>()]>>}
True
True
True
True
用时: 1.0154027938842773
# True表示cancel成功,loop循环在stop之后还需要再次开启循环,最后在close,否则还会引发异常

Task was destroyed but it is pending!
task: <Task pending coro=<love_cat() done, defined at 9协程停止.py:14> wait_for=<Future cancelled> cb=[_wait.<locals>._on_completion() at C:\python36\l
ib\asyncio\tasks.py:380]>
Task was destroyed but it is pending!
task: <Task pending coro=<love_cat() done, defined at 9协程停止.py:14> wait_for=<Future cancelled> cb=[_wait.<locals>._on_completion() at C:\python36\l
ib\asyncio\tasks.py:380]>
Task was destroyed but it is pending!
task: <Task pending coro=<love_cat() done, defined at 9协程停止.py:14> wait_for=<Future cancelled> cb=[_wait.<locals>._on_completion() at C:\python36\l
ib\asyncio\tasks.py:380]>

13.取消协程(2)

# 循环task,逐个cancel是一种方案,但像我们上面把task的列表封装在main函数中,m对main函数执行事件循环的调用
# 此时,main相当最外层的一个task,因此处理包装的main函数即可
import asyncio
import time

async def love_cat(t):
    print(f'等待{t}秒···')
    await asyncio.sleep(t)
    return f'等待了{t}秒'

async def main():
    coroutine1 = love_cat(1)
    coroutine2 = love_cat(2)
    coroutine3 = love_cat(4)
    tasks = [
        asyncio.ensure_future(coroutine1),
        asyncio.ensure_future(coroutine2),
        asyncio.ensure_future(coroutine3),
    ]
    dones,pending = await asyncio.wait(tasks)
    for future in dones:
        print('task result',future.result())

st_time = time.time()
loop = asyncio.get_event_loop()
task = loop.create_task(main())
try:
    loop.run_until_complete(task)
except KeyboardInterrupt as e:
    print(asyncio.Task.all_tasks())
    print(asyncio.gather(*asyncio.Task.all_tasks()).cancel())
    loop.stop()
    loop.run_forever()
finally:
    loop.close()
print('用时:',time.time()-st_time)
# 不按Ctrl+C触发异常的话

输出结果如下

等待1秒···
等待2秒···
等待4秒···
task result 等待了1秒
task result 等待了2秒
task result 等待了4秒
用时: 4.014608144760132
# 触发异常

等待1秒···
等待2秒···
等待4秒···
{<Task pending coro=<main() running at 9协程停止1.py:20> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000000002B5B888>()]>>, <Task
pending coro=<love_cat() running at 9协程停止1.py:8> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000000002B5B8E8>()]> cb=[_wait.<l
ocals>._on_completion() at C:\python36\lib\asyncio\tasks.py:380]>, <Task pending coro=<love_cat() running at 9协程停止1.py:8> wait_for=<Future pending
cb=[<TaskWakeupMethWrapper object at 0x0000000002B5B948>()]> cb=[_wait.<locals>._on_completion() at C:\python36\lib\asyncio\tasks.py:380]>, <Task pendi
ng coro=<love_cat() running at 9协程停止1.py:8> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000000002B5B9A8>()]> cb=[_wait.<locals
>._on_completion() at C:\python36\lib\asyncio\tasks.py:380]>}
True
用时: 1.0012037754058838

  

 
 

猜你喜欢

转载自www.cnblogs.com/traditional/p/9136618.html