Python协程asyncio模块的演变及高级用法

Python协程及asyncio基础知识

协程(coroutine)也叫微线程,是实现多任务的另一种方式,是比线程更小的执行单元,一般运行在单进程和单线程上。因为它自带CPU的上下文,它可以通过简单的事件循环切换任务,比进程和线程的切换效率更高,这是因为进程和线程的切换由操作系统进行。

Python实现协程的主要借助于两个库:asyncio和gevent。由于asyncio已经成为python的标准库了无需pip安装即可使用,这意味着asyncio作为Python原生的协程实现方式会更加流行。本文仅会介绍asyncio模块。如果大家对gevent也有需求,请留言,我会单独写篇文章介绍这个库的使用。

asyncio 是从Python3.4引入的标准库,直接内置了对协程异步IO的支持。asyncio 的编程模型本质是一个消息循环,我们一般先定义一个协程函数(或任务), 从 asyncio 模块中获取事件循环loop,然后把需要执行的协程任务(或任务列表)扔到 loop中执行,就实现了异步IO。

定义协程函数及执行方法的演变

在最早的Python 3.4中,协程函数是通过@asyncio.coroutine 和 yeild from 实现的, 如下所示。

 import asyncio
 
 @asyncio.coroutine
 def func1(i):
     print("协程函数{}马上开始执行。".format(i))
     yield from asyncio.sleep(2)
     print("协程函数{}执行完毕!".format(i))
 
 if __name__ == '__main__':
     # 获取事件循环
     loop = asyncio.get_event_loop()
 
     # 执行协程任务
     loop.run_until_complete(func1(1))
 
     # 关闭事件循环
     loop.close()

这里我们定义了一个func1的协程函数,我们可以使用asyncio.iscoroutinefunction来验证。定义好协程函数后,我们首先获取事件循环loop,使用它的run_until_complete方法执行协程任务,然后关闭loop。

 print(asyncio.iscoroutinefunction(func1(1))) # True

Python 3.5以后引入了async/await 语法定义协程函数,代码如下所示。每个协程函数都以async声明,以区别于普通函数,对于耗时的代码或函数我们使用await声明,表示碰到等待时挂起,以切换到其它任务。

 import asyncio
 
 # 这是一个协程函数
 async def func1(i):
     print("协程函数{}马上开始执行。".format(i))
     await asyncio.sleep(2)
     print("协程函数{}执行完毕!".format(i))
 
 if __name__ == '__main__':
     # 获取事件循环
     loop = asyncio.get_event_loop()
 
     # 执行协程任务
     loop.run_until_complete(func1(1))
 
     # 关闭事件循环
     loop.close()

Python 3.7之前执行协程任务都是分三步进行的,代码有点冗余。Python 3.7提供了一个更简便的asyncio.run方法,上面代码可以简化为:

 import asyncio
 
 async def func1(i):
     print(f"协程函数{i}马上开始执行。")
     await asyncio.sleep(2)
     print(f"协程函数{i}执行完毕!")
 
 if __name__ == '__main__':
     asyncio.run(func1(1))

注:Python自3.6版本起可以使用f-string来对字符串进行格式化了,相当于format函数的简化版。

创建协程任务的演变

前面的演示案例中,我们只执行了单个协程任务(函数)。实际应用中,我们先由协程函数创建协程任务,然后把它们加入协程任务列表,最后一起交由事件循环执行。

根据协程函数创建协程任务有多种方法,其中最新的是Python 3.7版本提供的asyncio.create_task方法,如下所示:

 # 方法1:使用ensure_future方法。future代表一个对象,未执行的任务。
 task1 = asyncio.ensure_future(func1(1))
 task2 = asyncio.ensure_future(func1(2))
 
 # 方法2:使用loop.create_task方法
 task1 = loop.create_task(func1(1))
 task2 = loop.create_task(func1(2))
 
 # 方法3:使用Python 3.7提供的asyncio.create_task方法
 task1 = asyncio.create_task(func1(1))
 task2 = asyncio.create_task(func1(2))

创建多个协程任务列表后,我们还要使用asyncio.wait方法收集协程任务,并交由事件循环处理执行。

 import asyncio
 
 async def func1(i):
     print(f"协程函数{i}马上开始执行。")
     await asyncio.sleep(2)
     print(f"协程函数{i}执行完毕!")
 
 
 async def main():
     tasks = []
     # 创建包含4个协程任务的列表
     for i in range(1, 5):
         tasks.append(asyncio.create_task(func1(i)))
         
     await asyncio.wait(tasks)
 
 if __name__ == '__main__':
     asyncio.run(main())

执行效果如下所示,你会发现4个协程任务并不是按顺序执行的。

对于收集多个协程任务,Python还提供了新的asyncio.gather方法,它的作用asyncio.wait方法类似,但更强大。如果列表中传入的不是create_task方法创建的协程任务,它会自动将函数封装成协程任务,如下所示:

 import asyncio
 
 async def func1(i):
     print(f"协程函数{i}马上开始执行。")
     await asyncio.sleep(2)
     print(f"协程函数{i}执行完毕!")
 
 async def main():
     tasks = []
     for i in range(1, 5):
         # 这里未由协程函数创建协程任务
         tasks.append(func1(i))
         
     # 注意这里*号。gather自动将函数列表封装成了协程任务。
     await asyncio.gather(*tasks)
 
 if __name__ == '__main__':
     asyncio.run(main())

获取协程任务执行结果

是的,gather方法有将函数封装成协程任务的能力,但这还并不是两者最主要的区别作用。两者更大的区别在协程任务执行完毕后对于返回结果的处理上。通常获取任务执行结果通常对于一个程序至关重要,因此我们有必要花更多时间详细了解这两个方法的使用。

asyncio.wait 会返回两个值:done 和 pending,done 为已完成的协程任务列表,pending 为超时未完成的协程任务类别,需通过task.result()方法可以获取每个协程任务返回的结果;而asyncio.gather 返回的是所有已完成协程任务的 result,不需要再进行调用或其他操作,就可以得到全部结果。

我们来看两个示例。现在修改我们的协程函数,通过return给它增加一个返回值。

通过asyncio.wait获取协程任务执行结果

 import asyncio
 
 async def func1(i):
     print(f"协程函数{i}马上开始执行。")
     await asyncio.sleep(2)
     return i
 
 async def main():
     tasks = []
     for i in range(1, 5):
         tasks.append(asyncio.create_task(func1(i)))
         
     # 获取任务执行结果。
     done, pending = await asyncio.wait(tasks)
     for task in done:
         print(f"执行结果: {task.result()}")
 
 if __name__ == '__main__':
     asyncio.run(main())

执行结果如下所示。你可以看到协程任务执行结果并不是按任务添加的顺序返回的。

通过asyncio.gather获取协程任务执行结果

继续修改我们的代码:

 #-*- coding:utf-8 -*-
 import asyncio
 
 async def func1(i):
     print(f"协程函数{i}马上开始执行。")
     await asyncio.sleep(2)
     return i
 
 async def main():
     tasks = []
     for i in range(1, 5):
         tasks.append(func1(i))
 
     results = await asyncio.gather(*tasks)
     for result in results:
         print(f"执行结果: {result}")
 
 if __name__ == '__main__':
     asyncio.run(main())

执行结果如下所示。协程任务执行结果与任务添加顺序完全一致。

现在你知道gather和wait方法的真正区别了吗?

  • gather具有把普通协程函数包装成协程任务的能力,wait没有。wait只能接收包装后的协程任务列表做参数。
  • 两者返回值不一样,wait返回的是已完成和未完成任务的列表,而gather直接返回协程任务执行结果。
  • gather返回的任务执行结果是有序的,wait方法获取的结果是无序的。

asyncio高级使用方法

给任务添加回调函数

我们还可以给每个协程任务通过add_done_callback的方法给单个协程任务添加回调函数,如下所示:

 #-*- coding:utf-8 -*-
 import asyncio
 
 async def func1(i):
     print(f"协程函数{i}马上开始执行。")
     await asyncio.sleep(2)
     return i
 
 # 回调函数
 def callback(future):
     print(f"执行结果:{future.result()}")
 
 async def main():
     tasks = []
     for i in range(1, 5):
         task = asyncio.create_task(func1(i))
         
         # 注意这里,增加回调函数
         task.add_done_callback(callback)
         tasks.append(task)
 
     await asyncio.wait(tasks)
 
 if __name__ == '__main__':
     asyncio.run(main())

设置任务超时

很多协程任务都是很耗时的,当你使用wait方法收集协程任务时,可通过timeout选项设置任务切换前单个任务最大等待时间长度,如下所示:

  # 获取任务执行结果,如下所示:
  done,pending = await asyncio.wait(tasks, timeout=10)

自省

  • asyncio.current_task: 返回当前运行的Task实例,如果没有正在运行的任务则返回 None。如果 loop 为 None 则会使用 get_running_loop()获取当前事件循环。
  • asyncio.all_tasks: 返回事件循环所运行的未完成的Task对象的集合。

以上就是Python协程asyncio模块的演变及高级用法的详细内容,更多关于Python协程asyncio模块的资料请关注我们其它相关文章!

(0)

相关推荐

  • python 使用事件对象asyncio.Event来同步协程的操作

    事件对象asyncio.Event是基于threading.Event来实现的. 事件可以一个信号触发多个协程同步工作, 例子如下: import asyncio import functools def set_event(event): print('setting event in callback') event.set() async def coro1(event): print('coro1 waiting for event') await event.wait() print(

  • python中使用asyncio实现异步IO实例分析

    1.说明 Python实现异步IO非常简单,asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持. asyncio的编程模型就是一个消息循环.我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO. 2.实例 import asyncio @asyncio.coroutine def wget(host): print('wget %s...' % host) connect = asynci

  • python asyncio 协程库的使用

    asyncio 是 python 力推多年的携程库,与其 线程库 相得益彰,更轻量,并且协程可以访问同一进程中的变量,不需要进程间通信来传递数据,所以使用起来非常顺手. asyncio 官方文档写的非常简练和有效,半小时内可以学习和测试完,下面为我的一段 HelloWrold,感觉可以更快速的帮你认识 协程 . 定义协程 import asyncio import time async def say_after(delay, what): await asyncio.sleep(delay)

  • Python中使用asyncio 封装文件读写

    前言 和网络 IO 一样,文件读写同样是一个费事的操作. 默认情况下,Python 使用的是系统的阻塞读写.这意味着在 asyncio 中如果调用了 f = file('xx') f.read() 会阻塞事件循环. 本篇简述如何用 asyncio.Future 对象来封装文件的异步读写. 代码在 GitHub.目前仅支持 Linux. 阻塞和非阻塞 首先需要将文件的读写改为非阻塞的形式.在非阻塞情况下,每次调用 read 都会立即返回,如果返回值为空,则意味着文件操作还未完成,反之则是读取的文件

  • python中asyncio异步编程学习

    1.   想学asyncio,得先了解协程 携程的意义: 计算型的操作,利用协程来回切换执行,没有任何意义,来回切换并保存状态 反倒会降低性能. IO型的操作,利用协程在IO等待时间就去切换执行其他任务,当IO操作结束后再自动回调,那么就会大大节省资源并提供性能,从而实现异步编程(不等待任务结束就可以去执行其他代码 2.协程和多线程之间的共同点和区别: 共同点: 都是并发操作,多线程同一时间点只能有一个线程在执行,协程同一时间点只能有一个任务在执行: 不同点: 多线程,是在I/O阻塞时通过切换线

  • Python  Asyncio模块实现的生产消费者模型的方法

    asyncio的关键字说明 event_loop事件循环:程序开启一个无限循环,把一些函数注册到事件循环上,当满足事件发生的时候,调用相应的协程函数 coroutine协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象,协程对象需要注册到事件循环,由事件循环调用. task任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含了任务的各种状态 future:代表将来执行或没有执行的任务结果.它和task上没有本质上的区

  • Python并发concurrent.futures和asyncio实例

    说明 Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码. 从Python3.2开始,标准库为我们提供了concurrent.futures模块,concurrent.futures 模块的主要特色是 ThreadPoolExecutor 和 ProcessPoolExecutor 类,这两个类实现的接口能分别在不同的线程或进程中执行可调 用的对象.这两个类在内部维护着一个工作线程或进程池,以及要执行的任务队列. Python 3.4

  • Python中的asyncio代码详解

    asyncio介绍 熟悉c#的同学可能知道,在c#中可以很方便的使用 async 和 await 来实现异步编程,那么在python中应该怎么做呢,其实python也支持异步编程,一般使用 asyncio 这个库,下面介绍下什么是 asyncio : asyncio 是用来编写 并发 代码的库,使用 async/await 语法. asyncio 被用作多个提供高性能 Python 异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等. asyncio 往往是构建 IO 密集型和

  • Python中asyncio模块的深入讲解

    1. 概述 Python中 asyncio 模块内置了对异步IO的支持,用于处理异步IO:是Python 3.4版本引入的标准库. asyncio 的编程模型就是一个消息循环.我们从 asyncio 块中直接获取一个 EventLoop 的引用,然后把需要执行的协程扔到 EventLoop 中执行,就实现了异步IO. 2. 用asyncio实现Hello world #!/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2019/1/9

  • Python中asyncio与aiohttp入门教程

    很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知道如何使用 Tornado.Twisted.Gevent 这类异步框架上,出现各种古怪的问题难以解决.而且使用了异步框架的部分同学,由于用法不对,感觉它并没牛逼到哪里去,所以很多同学做 Web 后端服务时还是采用 Flask.Django等传统的非异步框架. 从上两届 PyCon 技术大会看来,异步编程已经成了 Python 生态下一阶段的主旋律.如新兴的 Go.Rust.Eli

随机推荐