python asyncio 实现协程_python 异步IO( asyncio) 协程

创建task后,task在加入事件循环之前是pending状态,因为do_some_work中没有耗时的阻塞操作,task很快就执行完毕了。后面打印的finished状态。

asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)都可以创建一个task,run_until_complete的参数是一个futrue对象。当传入一个协程,其内部会自动封装成task,task是Future的子类。isinstance(task, asyncio.Future)将会输出True。

绑定回调

绑定回调,在task执行完毕的时候可以获取执行的结果,回调的最后一个参数是future对象,通过该对象可以获取协程返回值。如果回调需要多个参数,可以通过偏函数导入。

importtimeimportasyncio

now= lambda: time.time()

asyncdefdo_some_work(x):print('Waiting:', x)return 'Done after {}s'.format(x)defcallback(future):print('Callback:', future.result())

start=now()

coroutine= do_some_work(2)

loop=asyncio.get_event_loop()

task=asyncio.ensure_future(coroutine)

task.add_done_callback(callback)

loop.run_until_complete(task)print('TIME:', now() - start)

利用偏函数

defcallback(t, future):print('Callback:', t, future.result())

task.add_done_callback(functools.partial(callback,2))

可以看到,coroutine执行结束时候会调用回调函数。并通过参数future获取协程执行的结果。我们创建的task和回调里的future对象,实际上是同一个对象。

future 与 result

回调一直是很多异步编程的恶梦,程序员更喜欢使用同步的编写方式写异步代码,以避免回调的恶梦。回调中我们使用了future对象的result方法。前面不绑定回调的例子中,我们可以看到task有fiinished状态。在那个时候,可以直接读取task的result方法。

async defdo_some_work(x):print('Waiting {}'.format(x))return 'Done after {}s'.format(x)

start=now()

coroutine= do_some_work(2)

loop=asyncio.get_event_loop()

task=asyncio.ensure_future(coroutine)

loop.run_until_complete(task)print('Task ret: {}'.format(task.result()))print('TIME: {}'.format(now() - start))

可以看到输出的结果:

Waiting: 2Task ret: Done after 2s

TIME:0.0003650188446044922

阻塞和await

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

耗时的操作一般是一些IO操作,例如网络请求,文件读取等。我们使用asyncio.sleep函数来模拟IO操作。协程的目的也是让这些IO操作异步化。

importasyncioimporttime

now= lambda: time.time()

asyncdefdo_some_work(x):print('Waiting:', x)

await asyncio.sleep(x)return 'Done after {}s'.format(x)

start=now()

coroutine= do_some_work(2)

loop=asyncio.get_event_loop()

task=asyncio.ensure_future(coroutine)

loop.run_until_complete(task)print('Task ret:', task.result())print('TIME:', now() - start)

在 sleep的时候,使用await让出控制权。即当遇到阻塞调用的函数的时候,使用await方法将协程的控制权让出,以便loop调用其他的协程。现在我们的例子就用耗时的阻塞操作了。

并发和并行

并发和并行一直是容易混淆的概念。并发通常指有多个任务需要同时进行,并行则是同一时刻有多个任务执行。用上课来举例就是,并发情况下是一个老师在同一时间段辅助不同的人功课。并行则是好几个老师分别同时辅助多个学生功课。简而言之就是一个人同时吃三个馒头还是三个人同时分别吃一个的情况,吃一个馒头算一个任务。

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

importasyncioimporttime

now= lambda: time.time()

asyncdefdo_some_work(x):print('Waiting:', x)

await asyncio.sleep(x)return 'Done after {}s'.format(x)

start=now()

coroutine1= do_some_work(1)

coroutine2= do_some_work(2)

coroutine3= do_some_work(4)

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))for task intasks:print('Task ret:', task.result())print('TIME:', now() - start)

结果如下

Waiting: 1Waiting:2Waiting:4Task ret: Done after 1s

Task ret: Done after 2s

Task ret: Done after 4s

TIME:4.003541946411133

协程嵌套

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

importasyncioimporttime

now= lambda: time.time()

asyncdefdo_some_work(x):print('Waiting:', x)

await asyncio.sleep(x)return 'Done after {}s'.format(x)

asyncdefmain():

coroutine1= do_some_work(1)

coroutine2= do_some_work(2)

coroutine3= do_some_work(4)

tasks=[

asyncio.ensure_future(coroutine1),

asyncio.ensure_future(coroutine2),

asyncio.ensure_future(coroutine3)

]

dones, pendings=await asyncio.wait(tasks)for task indones:print('Task ret:', task.result())

start=now()

loop=asyncio.get_event_loop()

loop.run_until_complete(main())print('TIME:', now() - start)

如果使用的是 asyncio.gather创建协程对象,那么await的返回值就是协程运行的结果。

results = await asyncio.gather(*tasks)for result inresults:print('Task ret:', result)

不在main协程函数里处理结果,直接返回await的内容,那么最外层的run_until_complete将会返回main协程的结果。

async defmain():

coroutine1= do_some_work(1)

coroutine2= do_some_work(2)

coroutine3= do_some_work(2)

tasks=[

asyncio.ensure_future(coroutine1),

asyncio.ensure_future(coroutine2),

asyncio.ensure_future(coroutine3)

]return await asyncio.gather(*tasks)

start=now()

loop=asyncio.get_event_loop()

results=loop.run_until_complete(main())for result inresults:print('Task ret:', result)

或者返回使用asyncio.wait方式挂起协程。

async defmain():

coroutine1= do_some_work(1)

coroutine2= do_some_work(2)

coroutine3= do_some_work(4)

tasks=[

asyncio.ensure_future(coroutine1),

asyncio.ensure_future(coroutine2),

asyncio.ensure_future(coroutine3)

]returnawait asyncio.wait(tasks)

start=now()

loop=asyncio.get_event_loop()

done, pending=loop.run_until_complete(main())for task indone:print('Task ret:', task.result())

也可以使用asyncio的as_completed方法

async defmain():

coroutine1= do_some_work(1)

coroutine2= do_some_work(2)

coroutine3= do_some_work(4)

tasks=[

asyncio.ensure_future(coroutine1),

asyncio.ensure_future(coroutine2),

asyncio.ensure_future(coroutine3)

]for task inasyncio.as_completed(tasks):

result=await taskprint('Task ret: {}'.format(result))

start=now()

loop=asyncio.get_event_loop()

done=loop.run_until_complete(main())print('TIME:', now() - start)

协程停止

上面见识了协程的几种常用的用法,都是协程围绕着事件循环进行的操作。future对象有几个状态:

Pending

Running

Done

Cancelled

创建future的时候,task为pending,事件循环调用执行的时候当然就是running,调用完毕自然就是done,如果需要停止事件循环,就需要先把task取消。可以使用asyncio.Task获取事件循环的task

importasyncioimporttime

now= lambda: time.time()

asyncdefdo_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(2)

tasks=[

asyncio.ensure_future(coroutine1),

asyncio.ensure_future(coroutine2),

asyncio.ensure_future(coroutine3)

]

start=now()

loop=asyncio.get_event_loop()try:

loop.run_until_complete(asyncio.wait(tasks))exceptKeyboardInterrupt as e:print(asyncio.Task.all_tasks())for task inasyncio.Task.all_tasks():print(task.cancel())

loop.stop()

loop.run_forever()finally:

loop.close()print('TIME:', now() - start)

启动事件循环之后,马上ctrl+c,会触发run_until_complete的执行异常 KeyBorardInterrupt。然后通过循环asyncio.Task取消future。可以看到输出如下:

Waiting: 1Waiting:2Waiting:2{ wait_for=()]> cb=[_wait.._on_completion() at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py:374]>, wait_for=()]> cb=[_wait.._on_completion() at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py:374]>, wait_for=()]> cb=[_run_until_complete_cb() at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py:176]>, wait_for=()]> cb=[_wait.._on_completion() at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py:374]>}

True

True

True

True

TIME:0.8858370780944824

True表示cannel成功,loop stop之后还需要再次开启事件循环,最后在close,不然还会抛出异常:

Task was destroyed but it ispending!

task:

循环task,逐个cancel是一种方案,可是正如上面我们把task的列表封装在main函数中,main函数外进行事件循环的调用。这个时候,main相当于最外出的一个task,那么处理包装的main函数即可。

importasyncioimporttime

now= lambda: time.time()

asyncdefdo_some_work(x):print('Waiting:', x)

await asyncio.sleep(x)return 'Done after {}s'.format(x)

asyncdefmain():

coroutine1= do_some_work(1)

coroutine2= do_some_work(2)

coroutine3= do_some_work(2)

tasks=[

asyncio.ensure_future(coroutine1),

asyncio.ensure_future(coroutine2),

asyncio.ensure_future(coroutine3)

]

done, pending=await asyncio.wait(tasks)for task indone:print('Task ret:', task.result())

start=now()

loop=asyncio.get_event_loop()

task=asyncio.ensure_future(main())try:

loop.run_until_complete(task)exceptKeyboardInterrupt as e:print(asyncio.Task.all_tasks())print(asyncio.gather(*asyncio.Task.all_tasks()).cancel())

loop.stop()

loop.run_forever()finally:

loop.close()

不同线程的事件循环

很多时候,我们的事件循环用于注册协程,而有的协程需要动态的添加到事件循环中。一个简单的方式就是使用多线程。当前线程创建一个事件循环,然后在新建一个线程,在新线程中启动事件循环。当前线程不会被block。

from threading importThreaddefstart_loop(loop):

asyncio.set_event_loop(loop)

loop.run_forever()defmore_work(x):print('More work {}'.format(x))

time.sleep(x)print('Finished more work {}'.format(x))

start=now()

new_loop=asyncio.new_event_loop()

t= Thread(target=start_loop, args=(new_loop,))

t.start()print('TIME: {}'.format(time.time() -start))

new_loop.call_soon_threadsafe(more_work,6)

new_loop.call_soon_threadsafe(more_work,3)

启动上述代码之后,当前线程不会被block,新线程中会按照顺序执行call_soon_threadsafe方法注册的more_work方法,后者因为time.sleep操作是同步阻塞的,因此运行完毕more_work需要大致6 + 3

新线程协程

defstart_loop(loop):

asyncio.set_event_loop(loop)

loop.run_forever()

asyncdefdo_some_work(x):print('Waiting {}'.format(x))

await asyncio.sleep(x)print('Done after {}s'.format(x))defmore_work(x):print('More work {}'.format(x))

time.sleep(x)print('Finished more work {}'.format(x))

start=now()

new_loop=asyncio.new_event_loop()

t= Thread(target=start_loop, args=(new_loop,))

t.start()print('TIME: {}'.format(time.time() -start))

asyncio.run_coroutine_threadsafe(do_some_work(6), new_loop)

asyncio.run_coroutine_threadsafe(do_some_work(4), new_loop)

上述的例子,主线程中创建一个new_loop,然后在另外的子线程中开启一个无限事件循环。主线程通过run_coroutine_threadsafe新注册协程对象。这样就能在子线程中进行事件循环的并发操作,同时主线程又不会被block。一共执行的时间大概在6s左右。

master-worker主从模式

对于并发任务,通常是用生成消费模型,对队列的处理可以使用类似master-worker的方式,master主要用户获取队列的msg,worker用户处理消息。

为了简单起见,并且协程更适合单线程的方式,我们的主线程用来监听队列,子线程用于处理队列。这里使用redis的队列。主线程中有一个是无限循环,用户消费队列。

whileTrue:

task= rcon.rpop("queue")if nottask:

time.sleep(1)continueasyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)

给队列添加一些数据:

127.0.0.1:6379[3]> lpush queue 2(integer)1

127.0.0.1:6379[3]> lpush queue 5(integer)1

127.0.0.1:6379[3]> lpush queue 1(integer)1

127.0.0.1:6379[3]> lpush queue 1

可以看见输出:

Waiting 2Done2Waiting5Waiting1Done1Waiting1Done1Done5

我们发起了一个耗时5s的操作,然后又发起了连个1s的操作,可以看见子线程并发的执行了这几个任务,其中5s awati的时候,相继执行了1s的两个任务。

停止子线程

如果一切正常,那么上面的例子很完美。可是,需要停止程序,直接ctrl+c,会抛出KeyboardInterrupt错误,我们修改一下主循环:

try:whileTrue:

task= rcon.rpop("queue")if nottask:

time.sleep(1)continueasyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)exceptKeyboardInterrupt as e:print(e)

new_loop.stop()

可是实际上并不好使,虽然主线程try了KeyboardInterrupt异常,但是子线程并没有退出,为了解决这个问题,可以设置子线程为守护线程,这样当主线程结束的时候,子线程也随机退出。

new_loop =asyncio.new_event_loop()

t= Thread(target=start_loop, args=(new_loop,))

t.setDaemon(True)#设置子线程为守护线程

t.start()try:whileTrue:#print('start rpop')

task = rcon.rpop("queue")if nottask:

time.sleep(1)continueasyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)exceptKeyboardInterrupt as e:print(e)

new_loop.stop()

线程停止程序的时候,主线程退出后,子线程也随机退出才了,并且停止了子线程的协程任务。

aiohttp

在消费队列的时候,我们使用asyncio的sleep用于模拟耗时的io操作。以前有一个短信服务,需要在协程中请求远程的短信api,此时需要是需要使用aiohttp进行异步的http请求。大致代码如下:

server.py

importtimefrom flask importFlask, request

app= Flask(__name__)

@app.route('/')defindex(x):

time.sleep(x)return "{} It works".format(x)

@app.route('/error')deferror():

time.sleep(3)return "error!"

if __name__ == '__main__':

app.run(debug=True)

/接口表示短信接口,/error表示请求/失败之后的报警。

async-custoimer.py

importtimeimportasynciofrom threading importThreadimportredisimportaiohttpdefget_redis():

connection_pool= redis.ConnectionPool(host='127.0.0.1', db=3)return redis.Redis(connection_pool=connection_pool)

rcon=get_redis()defstart_loop(loop):

asyncio.set_event_loop(loop)

loop.run_forever()

asyncdeffetch(url):

async with aiohttp.ClientSession() as session:

async with session.get(url) as resp:print(resp.status)returnawait resp.text()

asyncdefdo_some_work(x):print('Waiting', x)try:

ret= await fetch(url='http://127.0.0.1:5000/{}'.format(x))print(ret)exceptException as e:try:print(await fetch(url='http://127.0.0.1:5000/error'))exceptException as e:print(e)else:print('Done {}'.format(x))

new_loop=asyncio.new_event_loop()

t= Thread(target=start_loop, args=(new_loop,))

t.setDaemon(True)

t.start()try:whileTrue:

task= rcon.rpop("queue")if nottask:

time.sleep(1)continueasyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)exceptException as e:print('error')

new_loop.stop()finally:pass

对于redis的消费,还有一个block的方法:

try:whileTrue:

_, task= rcon.brpop("queue")

asyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)exceptException as e:print('error', e)

new_loop.stop()finally:pass

使用 brpop方法,会block住task,如果主线程有消息,才会消费。测试了一下,似乎brpop的方式更适合这种队列消费的模型。

127.0.0.1:6379[3]> lpush queue 5(integer)1

127.0.0.1:6379[3]> lpush queue 1(integer)1

127.0.0.1:6379[3]> lpush queue 1

可以看到结果

Waiting 5Waiting1Waiting1

200

1It works

Done1

200

1It works

Done1

200

5It works

Done5

协程消费

主线程用于监听队列,然后子线程的做事件循环的worker是一种方式。还有一种方式实现这种类似master-worker的方案。即把监听队列的无限循环逻辑一道协程中。程序初始化就创建若干个协程,实现类似并行的效果。

importtimeimportasyncioimportredis

now= lambda: time.time()defget_redis():

connection_pool= redis.ConnectionPool(host='127.0.0.1', db=3)return redis.Redis(connection_pool=connection_pool)

rcon=get_redis()

asyncdefworker():print('Start worker')whileTrue:

start=now()

task= rcon.rpop("queue")if nottask:

await asyncio.sleep(1)continue

print('Wait', int(task))

await asyncio.sleep(int(task))print('Done', task, now() -start)defmain():

asyncio.ensure_future(worker())

asyncio.ensure_future(worker())

loop=asyncio.get_event_loop()try:

loop.run_forever()exceptKeyboardInterrupt as e:print(asyncio.gather(*asyncio.Task.all_tasks()).cancel())

loop.stop()

loop.run_forever()finally:

loop.close()if __name__ == '__main__':

main()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python AsyncioPython 3.4 版本之后引入的一个标准库,它提供了一种异步编程的解决方案,使得 Python 开发者可以更加方便地实现高效的异步 IO 操作。相比于传统的多线程或多进程的方式,异步编程可以更好地利用 CPU 和 IO 资源,提高程序的运行效率。 以下是 Python Asyncio 的一些基本概念和用法: 1. 异步异步指的是程序在执行 IO 操作时不会阻塞进程,而是在等待 IO 操作完成的同时可以执行其他任务。 2. 协程协程是一种轻量级的线程,可以在一个线程中并发执行多个协程任务,从而实现异步编程。 3. 事件循环:事件循环是 Asyncio 中的核心概念,它负责调度协程任务的执行,以及处理 IO 事件。 4. FutureFutureAsyncio 中用于异步编程的一种对象,它表示一个异步操作的结果,可以用于等待异步操作的完成。 5. TaskTaskFuture 的子类,表示一个协程任务,它封装了一个协程对象,并可以被事件循环调度执行。 以下是一个简单的使用 Asyncio 实现异步 IO 操作的例子: ```python import asyncio async def fetch_data(): print('Start fetching data...') await asyncio.sleep(1) # 模拟 IO 操作 print('Data fetched!') async def main(): task1 = asyncio.create_task(fetch_data()) task2 = asyncio.create_task(fetch_data()) await task1 await task2 asyncio.run(main()) ``` 在这个例子中,我们定义了一个 fetch_data 协程函数,它模拟了一个 IO 操作,并在操作完成后打印了一条消息。在 main 函数中,我们使用 asyncio.create_task 创建了两个任务,并等待它们的完成。由于这两个任务是并发执行的,所以它们的完成顺序是不确定的。 这只是 Asyncio一个简单示例,如果你想深入了解 Asyncio 的使用和原理,可以参考 Python 官方文档或者一些优秀的 Asyncio 教程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值