asyncio基础篇01

本文介绍了asyncio的基础知识,包括事件循环、协程、future和task的概念。async关键字用于定义协程,await用于挂起异步调用。文章通过示例讲解了如何使用asyncio实现回调函数、并发任务以及协程的嵌套。此外,还详细对比了gather和wait在处理并发任务时的差异和应用场景。
摘要由CSDN通过智能技术生成

在asyncio的前篇中我们已经知道yield到asyncIO的演化路线
现在我们就来了解asyncio的基本使用

event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数(协程)注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
future 对象:代表将来执行或没有执行的任务的结果。它和task上没有本质的区别
task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。Task 对象是 Future 的子类,它将 coroutine 和 Future 联系在一起,将 coroutine 封装成一个 Future 对象。
async/await 关键字:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。其作用在一定程度上类似于yield。

import asyncio

async def hello(name):
print(‘Hello,’, name)

#定义协程对象
coroutine = hello(“World”)

#定义事件循环对象容器
loop = asyncio.get_event_loop()
#task = asyncio.ensure_future(coroutine)

#将协程转为task任务
task = loop.create_task(coroutine)

#将task任务扔进事件循环对象中并触发
loop.run_until_complete(task)

await用于挂起阻塞的异步调用接口。其作用在一定程度上类似于yield。
注意这里是,一定程度上,意思是效果上一样(都能实现暂停的效果),但是功能上却不兼容。就是你不能在生成器中使用await,也不能在async 定义的协程中使用yield。

在这里插入图片描述
除此之外呢,还有一点很重要的。
yield from 后面可接 可迭代对象,也可接future对象/协程对象;
await 后面必须要接 future对象/协程对象

绑定回调函数

异步IO的实现原理,就是在IO高的地方挂起,等IO结束后,再继续执行。在绝大部分时候,我们后续的代码的执行是需要依赖IO的返回值的,这就要用到回调了。
回调的实现,有两种,一种是绝大部分程序员喜欢的,利用的同步编程实现的回调。 这就要求我们要能够有办法取得协程的await的返回值。

import asyncio
import time


async def _sleep(x):
    time.sleep(2)
    return '暂停{}秒'.format(x)


loop = asyncio.get_event_loop()
task = asyncio.ensure_future(_sleep(2))
loop.run_until_complete(task)

#取得返回结果
print(task.result())

运行结果
暂停2秒

  • 还有一种是通过asyncio自带的添加回调函数功能来实现。
import asyncio
import time


async def _sleep(x):
    time.sleep(2)
    return '暂停{}秒'.format(x)

def callback(future):
    print("这里是回调函数,获取返回结果是:",future.result())

loop = asyncio.get_event_loop()
task = asyncio.ensure_future(_sleep(2))

#回调函数,自动将future传递进去
task.add_done_callback(callback)

loop.run_until_complete(task)

运行结果:
这里是回调函数,获取返回结果是: 暂停2秒

上面讲述的是协程中的单任务,而很多情况下我们其实是要进行多任务的,否则异步IO就没意义了

协程中的并发
asyncio实现并发,就需要多个协程来完成任务,每当有任务阻塞的时候就await,然后其他协程继续工作。
我们采用wait和gather来处理多个并发

import asyncio

async def do_some_work(x):
    print('waiting',x)
    await asyncio.sleep(x)
    return 'Done after {}s'.format(x)

#协程对象
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(3)

tasks = [
    asyncio.ensure_future(coroutine1),
    asyncio.ensure_future(coroutine2),
    asyncio.ensure_future(coroutine3)
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

#loop.run_until_complete(asyncio.gather(*tasks))
for i in tasks:
    print(i.result())


协程中的嵌套

使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来。

import asyncio


# 用于内部的协程函数
async def do_some_work(x):
    print('Waiting', x)
    await asyncio.sleep(x)
    return 'Done after {}s'.format(x)


# 用于外部的协程函数
async def main():
    # 创建协程对象
    coroutine1 = do_some_work(1)
    coroutine2 = do_some_work(2)
    coroutine3 = do_some_work(3)

    tasks = [
        asyncio.ensure_future(coroutine1),
        asyncio.ensure_future(coroutine2),
        asyncio.ensure_future(coroutine3)
    ]
    # dones表示已经完成的任务
    # pendings 表示未完成的任务,因为wait是可以设定等待时间的
    dones, pendings = await asyncio.wait(tasks)
    for i in dones:
        print("task ret:", i.result())


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

源码当中也是嵌套的:

asyncio.await()的源码# 内部协程函数
async def _wait(fs, timeout, return_when, loop):
    assert fs, 'Set of Futures is empty.'
    waiter = loop.create_future()
    timeout_handle = None
    if timeout is not None:
        timeout_handle = loop.call_later(timeout, _release_waiter, waiter)
    counter = len(fs)

    def _on_completion(f):
        nonlocal counter
        counter -= 1
        if (counter <= 0 or
            return_when == FIRST_COMPLETED or
            return_when == FIRST_EXCEPTION and (not f.cancelled() and
                                                f.exception() is not None)):
            if timeout_handle is not None:
                timeout_handle.cancel()
            if not waiter.done():
                waiter.set_result(None)

    for f in fs:
        f.add_done_callback(_on_completion)

    try:
        await waiter
    finally:
        if timeout_handle is not None:
            timeout_handle.cancel()

    done, pending = set(), set()
    for f in fs:
        f.remove_done_callback(_on_completion)
        if f.done():
            done.add(f)
        else:
            pending.add(f)
    return done, pending

# 外部协程函数
async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
    if futures.isfuture(fs) or coroutines.iscoroutine(fs):
        raise TypeError(f"expect a list of futures, not {type(fs).__name__}")
    if not fs:
        raise ValueError('Set of coroutines/Futures is empty.')
    if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):
        raise ValueError(f'Invalid return_when value: {return_when}')

    if loop is None:
        loop = events.get_event_loop()

    fs = {ensure_future(f, loop=loop) for f in set(fs)}
    # 【重点】:await一个内部协程
    return await _wait(fs, timeout, return_when, loop)

协程中的状态
生成器的时候,有提及过生成器的状态。同样,在协程这里,我们也了解一下协程(准确的说,应该是Future对象,或者Task任务)有哪些状态。
Pending:创建future,还未执行
Running:事件循环正在调用执行任务
Done:任务执行完毕
Cancelled:Task被取消后的状态

gather与wait的的深入理解

还是照例用例子来说明,先定义一个协程函数
import asyncio

async def factorial(name, number):
f = 1
for i in range(2, number+1):
print(“Task %s: Compute factorial(%s)…” % (name, i))
await asyncio.sleep(1)
f *= i
print(“Task %s: factorial(%s) = %s” % (name, number, f))

  • 接受参数方面的差异:

asyncio.wait
接收的tasks,必须是一个list对象,这个list对象里,存放多个的task。
它可以这样,用asyncio.ensure_future转为task对象

tasks=[
       asyncio.ensure_future(factorial("A", 2)),
       asyncio.ensure_future(factorial("B", 3)),
       asyncio.ensure_future(factorial("C", 4))
]

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.wait(tasks))

也可以这样,不转为task对象。
loop = asyncio.get_event_loop()

tasks=[
       factorial("A", 2),
       factorial("B", 3),
       factorial("C", 4)
]

loop.run_until_complete(asyncio.wait(tasks))

asyncio.gather
接收的就比较广泛了,他可以接收list对象,但是 * 不能省略

tasks=[
       asyncio.ensure_future(factorial("A", 2)),
       asyncio.ensure_future(factorial("B", 3)),
       asyncio.ensure_future(factorial("C", 4))
]

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.gather(*tasks))

还可以这样,和上面的 * 作用一致,这是因为asyncio.gather()的第一个参数是 *coros_or_futures,它叫 非命名键值可变长参数列表,可以集合所有没有命名的变量。
loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.gather(
    factorial("A", 2),
    factorial("B", 3),
    factorial("C", 4),
))

甚至还可以这样
loop = asyncio.get_event_loop()

group1 = asyncio.gather(*[factorial("A" ,i) for i in range(1, 3)])
group2 = asyncio.gather(*[factorial("B", i) for i in range(1, 5)])
group3 = asyncio.gather(*[factorial("B", i) for i in range(1, 7)])

loop.run_until_complete(asyncio.gather(group1, group2, group3))
  • 返回结果不同

asyncio.wait 返回dones和pendings
dones:表示已经完成的任务
pendings:表示未完成的任务
如果我们需要获取,运行结果,需要手工去收集获取。

asyncio.gather
它会把值直接返回给我们,不需要手工去收集。
results = await asyncio.gather(*tasks)

for result in results:
print('Task ret: ', result)

  • wait有控制功能,是wait的优点
import asyncio
import random


async def coro(tag):
    await asyncio.sleep(random.uniform(0.5, 5))


loop = asyncio.get_event_loop()

tasks = [coro(i) for i in range(1, 11)]
# 【控制运行任务数】:运行第一个任务就返回
# FIRST_COMPLETED :第一个任务完全返回
# FIRST_EXCEPTION:产生第一个异常返回
# ALL_COMPLETED:所有任务完成返回 (默认选项)
dones, pendings = loop.run_until_complete(asyncio.wait(tasks,return_when="FIRST_COMPLETED"))

print("第一次完成的任务量:",len(dones))

dones2,pendings2 = loop.run_until_complete(asyncio.wait(pendings,timeout=1))

print("第二次完成的任务量:",len(dones2))

dones3,pendings3 = loop.run_until_complete(asyncio.wait(pendings2))

print("第三次完成的任务量:",len(dones3))

loop.close()

运行结果:
第一次完成的任务数: 1
第二次完成的任务数: 4
第三次完成的任务数: 5

以上是asyncio的基本用法,而其实在实际当中asyncio并不是单独使用,现在很多异步开发的库都是以asyncio为基础的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值