python中协程异步IO(asyncio)详解(一)

异步IO:就是发起一个IO操作(如:网络请求,文件读写等),这些操作一般是比较耗时的,不用等待它结束,可以继续做其他事情,结束时会发来通知。

协程:又称为微线程,在一个线程中执行,执行函数时可以随时中断,由程序(用户)自身控制,执行效率极高,与多线程比较,没有切换线程的开销和多线程锁机制。

协程基础

从语句上看,协程和生成器类似,都是包含了yield关键字,不同之处在于协程中yield关键词通常出现在=右边,可以产出值a(y = yield a)或不产出值时为None(y = yield)。调用方可以用send函数发送值给协程。

激活协程时在yield处暂停,等待调用方发送数据,下次继续在yield暂停。从根本上看,yield是流程控制的工具,可以实现协作式多任务,这也是后面讲解异步IO的基础。

最简单的协程示例

使用协程时需要预激活(next函数)后才能使用send发送值。(a = yield b),next时会产出yield右边的值b,send时接收值的是yield左边的值a

示例代码

def cor_example(name):
    print('start coroutine name: ', name)
    x = yield name
    print('send值: ', x)
coro = cor_example('lyy')
print('next 返回的值: ', next(coro))
print('send 返回的值: ', coro.send(6))

输出结果

start coroutine name: lyy

next 返回的值: lyy

send值: 6

Traceback (most recent call last):

File "D:/project/test/test_xc.py", line 9, in <module>

print('send 返回的值: ', coro.send(6))

StopIteration

Process finished with exit code 1

必须先调用next()函数预激活协程,不然send()函数无法使用。

调用next()时,产出yield右边的值后暂停不再往yield的下一行执行(一般不需要next产出值),等待send的到来,调用send()时,产出值赋给x(可对x作进一步处理),并往下运行。

协程结束时会跟生成器一样抛出StopIteration的异常给调用方,调用方可以捕获它后处理。

让协程返回值以及yield from说明

获取协程的返回值

当结束协程时,会返回返回值,调用方会抛出StopIteration异常,返回值就在异常对象的value属性中

示例代码

def cor_example2(name):
    print('start coroutine name: ', name)
    while True:
        x = yield name
        if x is None:
            return 'xxxxx: lyy'
        print('send值: ', x)
coro = cor_example2('lyy')
next(coro)
print('send 返回的值: ', coro.send(6))
try:
    coro.send(None)
except StopIteration as e:
    print('返回值: ', e.value)

结果如下

start coroutine name: lyy

send值: 6

send 返回的值: lyy

返回值: xxxxx: lyy

Process finished with exit code 0

yield from 说明

yield from跟for循环很相似,但功能更多一些,不信你看下面代码

def for_test():
    for i in range(3):
        yield i
print(list(for_test()))
def yield_from_test():
    yield from range(3)
print(list(yield_from_test()))

结果:

[0, 1, 2]

[0, 1, 2]

其实yield from内部会自动捕获StopIteration异常,并把异常对象的value属性变成yield from表达式的值。

yield from x 表达式内部首先是调用iter(x),然后再调用next(),因此x是任何的可迭代对象。

yield from 的主要功能就是打开双向通道,把最外层的调用方和最内层的子生成器连接起来。

下面代码展示:调用方发送的值在yield from表达式处直接传递给子生成器,并在yield from处等待子生成器的返回

def coroutine_example(name):
    print('start coroutine...name:', name)
    x = yield name #调用next()时,产出yield右边的值后暂停;调用send()时,产出值赋给x,并往下运行
    print('send值:', x)
    return 'csdnId: mtck'
def grouper2():
    result2 = yield from coroutine_example('mtck') #在此处暂停,等待子生成器的返回后继续往下执行
    print('result2的值:', result2)
    return result2
def grouper():
    result = yield from grouper2()  # 在此处暂停,等待子生成器的返回后继续往下执行
    print('result的值:', result)
    return result
def main():
    g = grouper()
    next(g)
    try:
        g.send(10)
    except StopIteration as e:
        print('返回值:', e.value)
if __name__ == '__main__':
    main()

运行结果

start coroutine...name: mtck

send值: 10

result2的值: csdnId: mtck

result的值: csdnId: mtck

返回值: csdnId: mtck

从上面也可看到yield from起到一个双向通道的作用,同时子生成器也可使用yield from调用另一个子生成器,一直嵌套下去直到遇到yield表达式结束链式。

yield from一般用于asyncio模块做异步IO

异步IO(asyncio)

从上面我们知道了协程的基础,异步IO的asyncio库使用事件循环驱动的协程实现并发。用户可主动控制程序,在认为耗时IO处添加await(yield from)。

在asyncio库中,协程使用@asyncio.coroutine装饰,使用yield from来驱动,在python3.5中作了如下更改:

@asyncio.coroutine -> async

yield from -> await

asyncio中几个重要概念

1.事件循环

管理所有的事件,在整个程序运行过程中不断循环执行并追踪事件发生的顺序将它们放在队列中,空闲时调用相应的事件处理者来处理这些事件。

2.Future

Future对象表示尚未完成的计算,还未完成的结果

3.Task

是Future的子类,作用是在运行某个任务的同时可以并发的运行多个任务。

asyncio.Task用于实现协作式多任务的库,且Task对象不能用户手动实例化,通过下面2个函数创建:

asyncio.async()

loop.create_task() 或 asyncio.ensure_future()

最简单的异步IO示例

run_until_complete():

阻塞调用,直到协程运行结束才返回。参数是future,传入协程对象时内部会自动变为future

asyncio.sleep():

模拟IO操作,这样的休眠不会阻塞事件循环,前面加上await后会把控制权交给主事件循环,在休眠(IO操作)结束后恢复这个协程。

提示:若在协程中需要有延时操作,应该使用 await asyncio.sleep(),而不是使用time.sleep(),

因为使用time.sleep()后会释放GIL,阻塞整个主线程,从而阻塞整个事件循环。

示例代码

import asyncio
async def coroutine_example():
    await asyncio.sleep(1)
    print('csdn: mtck')
coro = coroutine_example()
loop = asyncio.get_event_loop()
loop.run_until_complete(coro)
loop.close()

运行结果

会暂停一秒后,打印csdn: mtck

创建Task

loop.create_task():

接收一个协程,返回一个asyncio.Task的实例,也是asyncio.Future的实例,毕竟Task是Future的子类。返回值可直接传入run_until_complete()

返回的Task对象可以看到协程的运行情况

示例代码

import asyncio
async def coroutine_example():
    await asyncio.sleep(1)
    print('csdnId: mtck')
coro = coroutine_example()
loop = asyncio.get_event_loop()
task = loop.create_task(coro)
print('运行情况:', task)
loop.run_until_complete(task)
print('再看下运行情况:', task)
loop.close()

运行结果

从结果可看到,当task为finished状态时,有个result()的方法,我们可以通过这个方法来获取协程的返回值

运行情况: <Task pending name='Task-1' coro=<coroutine_example() running at D:/project/test/test_xc.py:79>>

csdnId: mtck

再看下运行情况: <Task finished name='Task-1' coro=<coroutine_example() done, defined at D:/project/test/test_xc.py:79> result=None>

获取协程返回值

有2种方案可以获取返回值。

第1种方案:通过task.result()

可通过调用 task.result() 方法来获取协程的返回值,但是只有运行完毕后才能获取,若没有运行完毕,result()方法不会阻塞去等待结果,

而是抛出 asyncio.InvalidStateError 错误

示例代码

import asyncio
async def coroutine_example():
    await asyncio.sleep(1)
    return 'csdnId: mtck'
coro = coroutine_example()
loop = asyncio.get_event_loop()
task = loop.create_task(coro)
print('运行情况:', task)
try:
    print('返回值:', task.result())
except asyncio.InvalidStateError:
    print('task状态未完成,捕获了 InvalidStateError 异常')
loop.run_until_complete(task)
print('再看下运行情况:', task)
print('返回值:', task.result())
loop.close()

运行结果

我们可以看到:只有task状态运行完成时才能捕获返回值

运行情况: <Task pending name='Task-1' coro=<coroutine_example() running at D:/project/test/test_xc.py:95>>

task状态未完成,捕获了 InvalidStateError 异常

再看下运行情况: <Task finished name='Task-1' coro=<coroutine_example() done, defined at D:/project/test/test_xc.py:95> result='csdnId: mtck'>

返回值: csdnId: mtck

第2种方案:通过add_done_callback()回调

示例代码

import asyncio
def my_callback(future):
    print('返回值:', future.result())
async def coroutine_example():
    await asyncio.sleep(1)
    return 'csdnId: mtck'
coro = coroutine_example()
loop = asyncio.get_event_loop()
task = loop.create_task(coro)
task.add_done_callback(my_callback)
loop.run_until_complete(task)
loop.close()

运行结果

返回值: csdnId: mtck

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值