Asyncio初体验

Asyncio在Python中提供的API很复杂,其旨在替不同群体的人解决不同的问题,也正是由于这个原因,所以很难区分重点。

可以根据asyncio在Python中的特性,将其划分为两大主要群体:
1. 应用(最终用户)开发者,想要在应用开发中使用asyncio;
2. 框架开发者,制作框架或库以供应用开发者在他们的开发中使用。

在asyncio社区中大部分的问题基本都与这两个部分相关,例如,asyncio的官方文档更像是给框架开发者使用的,而非应用开发者,这导致应用开发者在阅读文档时很容易被其复杂性所震撼,给读者一种错觉就是在使用它之前,得看完全部的文档。

QuickStart

不需要关心官方文档的内容,要掌握asyncio库比想象中要容易。

PEP 492的作者、async Python的主要贡献者——Yury Selivanov——曾说过,asyncio的很多API都是给框架开发者提供的,应用开发人员需要掌握的只是所有API中的一小部分。

在本节我们将研究这些核心特性,并了解如何在Python中使用基于事件loop的编程,以此实现基本的异步。

要成为一个掌握asyncio的应用开发者,你需要知道的东西其实可以用一个小例子来展示。

import time
import asyncio

async def main():
    print(f'{time.ctime()} Hello')
    await asyncio.sleep(1.0)
    print(f'{time.ctime()} Goodbye')
    loop.stop()    # 1

loop = asyncio.get_event_loop()    # 2
loop.create_task(main())    # 3
loop.run_forever()    # 4
pending = asyncio.Task.all_tasks(loop=loop)
group = asyncio.gather(*pending, return_exceptions=True)    # 5
loop.run_until_complete(group)    # 6
loop.close()    # 7
λ  python quickstart.py
Fri Sep 28 19:39:33 2018 Hello
Fri Sep 28 19:39:34 2018 Goodbye
  1. 通常用于信号控制,暂停循环,但循环可以重新启用不会消失;
  2. 在运行协程之前获得一个循环实例,只要是在单线程中,这个实例就是单例的;
  3. 只有调用这个方法,协程才能被执行,该调用返回的task对象可以用于获取任务状态、结果,或可通过task.cancel()取消任务;
  4. 实现循环运行的方法之一,会阻塞当前线程(通常是主线程);
  5. 通常习惯是在程序入口处执行loop.run_forever()方法,在收到停止信号时停止循环,然后收集那些还未完成的task,调用loop.run_until_complete()方法等待其执行完毕,但更多的是用这个方法收集协程任务并取消它们,然后等待其执行完毕;
  6. 实现循环运行的方法之一,同样阻塞当前线程,其保持循环运行直到其上调度的协程完成;
  7. 通常在最后调用,必须在调用loop.stop()方法的基础上使用,会导致循环永久消失。

上述例子漏了一些东西,其中最重要的是如何运行阻塞函数,我们知道协程就是函数中使用了await关键字进行切换,但在当前async def还没获得广泛支持前,使用阻塞函数/库是不可避免的。

为此,asyncio提供了一个与concurrent.futures包中的API类似的API,它提供了ThreadPoolExecutorProcessPoolExecutor,默认基于线程,但很容易用基于进程的替换,这里面有些特殊的地方要注意。

import time
import asyncio

async def main():
    print(f'{time.ctime()} Hello')
    await asyncio.sleep(1.0)
    print(f'{time.ctime()} Goodbye')
    loop.stop()

def blocking(): # 1
    time.sleep(0.5) # 2
    print(f'{time.ctime()} Hello from a thread!')

loop = asyncio.get_event_loop()
loop.create_task(main())
loop.run_in_executor(None, blocking)    # 3

loop.run_forever()
pending = asyncio.Task.all_tasks(loop=loop) # 4
group = asyncio.gather(*pending)
loop.run_until_complete(group)
loop.close()
λ  python quickstart_exe.py
Fri Sep 28 20:21:21 2018 Hello
Fri Sep 28 20:21:22 2018 Hello from a thread!
Fri Sep 28 20:21:22 2018 Goodbye
  1. 这个函数调用了常规的sleep(),这会阻塞主线程并阻止loop运行,我们不能使这个函数变成协程,更糟糕的是不能在主线程运行loop时调用它,解决办法是用一个executor来运行它;
  2. 注意一点,这个sleep运行时间比协程中的sleep运行时间要短,后文再讨论如果长的话会发生什么;
  3. 该方法帮助我们在事件loop里用额外的线程或进程执行函数,这个方法的返回值是一个Future对象,意味着可以用await来切换它;
  4. 挂起的task中不包含前面的阻塞函数,并且这个方法只返回task对象,绝对不会返回Future对象。

好了,通过上面的学习,已经掌握了应用开发者对于asyncio库需要的最重要的部分,接下来将拓展知识并对API进行层次理解,这会让你更容易理解如何从文档中获取信息。

Asyncio全览

从前面一节发现,应用开发者只需几个命令就可以使用asyncio,但不幸的是官方文档巨量的API和扁平化的显示格式,让人很难分清哪些命令更通用,哪些命令是向框架开发者提供的,框架开发者可以通过文档寻找钩子并连接到其框架中,接下来我们从框架开发者的角度来看他们如何构建新的异步兼容库。

Level Concept Implementation
9 Network: streams StreamReader & StreamWriter
8 Network: TCP & UDP Protocol
7 Network: transports BaseTransport
6 tools asyncio.Queue
5 subprocesses & threads run_in_executor(), asyncio.subprocess
4 tasks asyncio.Task
3 futures asyncio.Future
2 event loop BaseEventLoop
1 coroutines async def & await

上表中加粗字体对于应用开发者最重要,级别分为9级,1级最基础。

  1. 一级,设计第三方框架的最低级别,但在流行的CurioTrio中并不常用,它们只依赖于Python中的本地协程,不依赖asyncio库模块;
  2. 二级,事件loop被分离出来,因此可以对其进行替换,比如uvloop实现了比标准库更快的循环;
  3. 三四级,带来了Future和Task对象,Task是Future的子类,可将它们简单地视作同一个等级,Future对象表示某种正在进行的动作,其将通过事件循环的notification返回结果,而Task对象表示运行在事件循环上的协程,简单理解为Future是“循环感知”的,Task是“循环感知”+“异步感知”的,应用开发更多地使用Task,框架开发者的使用比例要看代码细节;
  4. 五级,有关启动方面工具,并等待运行在单独的线程或进程上的工作;
  5. 六级,附加的异步通信工具,比如asyncio.Queue,提供与Queue模块类似的API,但原版的get和put方法会阻塞线程,因此这里提供的队列通过增加wait关键字以支持异步;
  6. 七到九级,网络IO层级,对应用开发者来说Stream十分方便,Protocol提供比Stream更细粒度的API,所有能使用Stream的地方都能用Protocol代替,除非要创建一个定制传输协议的框架供他人使用,否则几乎用不到Transport。

小结

在QuickStart中,掌握了快速上手的几个API;现在,对整个asyncio有了清晰的层级划分。这里再对上述知识进行强调:

  1. 第一级,知道如何写async def函数,如何使用await关键字来调用执行其它协程;
  2. 第二级,知道如何进行事件loop开关、交互;
  3. 第五级,知道如何在事件loop中运行阻塞程序,因为大多数第三方库都不支持异步,比如ORM库;
  4. 第六级,协程间数据通信使用asyncio.Queue等;
  5. 第九级,Streams的API提供了最简单的方式来使用socket进行网络通信。

如果使用提供异步兼容的第三方库,如aiohttp,那么就不用直接使用asyncio的网络层,但这会导致对第三方库的依赖。

随着Python的发展,asyncio库以后可能会提供更多的API,现在也只是大致地分了几个层级。

最新文章

  1. Android四种基本布局(LinearLayout \ RelativeLayout \ FrameLayout \ TableLayout)
  2. 使用“Empty 模式”改进 Null Object
  3. NOIP2013,复赛及同步赛,报名及比赛,专题页面
  4. [动态规划]状态压缩DP小结
  5. Longest Increasing Subsequence(DP)
  6. 强大的字符串格式化函数 - format
  7. 去掉cajviewer 右上角的“中国知网数字出版物超市
  8. ubantu命令安装banner
  9. POJ3260:The Fewest Coins(混合背包)
  10. Linux常见目录作用
  11. QT:使用“状态模式”绘制界面
  12. python日志记录-logging模块
  13. 照片详细解释YUV420数据格式
  14. 【转】解决UpdatePanel 与 jQuery的冲突
  15. Apache启动不了httpd: apr_sockaddr_info_get() failed xgp
  16. Jenkins + Ansible + Gitlab之gitlab篇
  17. AX_Query
  18. No Spring WebApplicationInitializer types detected on classpath
  19. 理解call及apply
  20. Oracle FM FM09999999 确保8位数字 即使全是0

热门文章

  1. 极致 Web 性能 —— SPA 性能指南
  2. Turn on and off trigger events 生效控制
  3. json-gson 解析泛型及解析null
  4. AC日记——队列安排 洛谷 P1160
  5. QUICK START GIT
  6. Ceres Solver: 高效的非线性优化库(一)
  7. 经验分享 | Burpsuite抓取非HTTP流量
  8. JAVA Eclipse开发Android如何让超出界面的部分自动显示滚动条
  9. 微信小程序-获取用户信息(getUserInfo)
  10. 关于使用Axure RP进行原型开发的一些心得体会