asyncio循环引擎 (CPython >= 3.4, uWSGI >= 2.0.4)

警告

状态:实验中,有许多隐含式,特别是关于WSGI标准

asyncio 插件公开了一个建立在 asyncio CPython API (https://docs.python.org/3.4/library/asyncio.html#module-asyncio)顶部的一个循环引擎。

由于uWSGI并不是基于回调的,因此你需要一个挂起引擎(目前只支持“greenlet”)来管理WSGI callable。

为什么不把WSGI callable映射到一个协程呢?

理由很简单:这会以各种可能的方式终端。(这里就不深入细节了。)

出于这个原因,每个uWSGI核被映射到一个greenlet (运行WSGI回调)上。

这个greenlet在asyncio事件循环中注册事件和协程。

Callable VS. 协程

当开始使用asyncio时,你可能会对Callable和协程之间感到困惑。

当一个特定的事件引发的时候(例如,当一个文件描述符准备用于读的时候),会执行Callable。它们基本上是在主greenlet中执行的标准函数 (最终,它们可以切换控制回特定的uWSGI核上)。

协程更复杂:它们很接近greenlet,但在内部,它们运行在Python帧之上,而不是C堆栈上。自一个Python程序员看来,协程是非常特别的生成器,你的WSGI callable可以生成协程。

利用asyncio支持构建uWSGI

可以在官方源代码树(也将构建greenlet支持)中找到一个’asyncio’构建配置文件。

  1. CFLAGS="-I/usr/local/include/python3.4" make PYTHON=python3.4 asyncio

或者

  1. CFLAGS="-I/usr/local/include/python3.4" UWSGI_PROFILE="asyncio" pip3 install uwsgi

一定要使用Python 3.4+作为Python版本,并且添加greenlet include目录到 CFLAGS (如果你从发行包中安装了greenlet支持,那么这可能并不需要)。

第一个例子:一个简单的回调

让我们从一个简单的WSGI callable开始,它在该 callable返回之后2秒后触发一个函数(神奇!)。

  1. import asyncio
  2.  
  3. def two_seconds_elapsed():
  4. print("Hello 2 seconds elapsed")
  5.  
  6. def application(environ, start_response):
  7. start_response('200 OK', [('Content-Type','text/html')])
  8. asyncio.get_event_loop().call_later(2, two_seconds_elapsed)
  9. return [b"Hello World"]

一旦被调用,那么该应用函数将在asyncio事件循环中注册一个 callable,然后会返回到客户端。

在2秒后,事件循环将会运行该函数。

你可以这样运行这个例子:

  1. uwsgi --asyncio 10 --http-socket :9090 --greenlet --wsgi-file app.py

—asyncio 是一个快捷方式,它启用10个uWSGI异步核,让你能够用一个单一的进程就可以管理多达10个并发请求。

但是,如何在WSGI callable中等待一个回调完成呢?我们可以使用greenlet来挂起我们的WSGI函数 (记住,我们的WSGI callable是封装在一个greenlet中的):

  1. import asyncio
  2. import greenlet
  3.  
  4. def two_seconds_elapsed(me):
  5. print("Hello 2 seconds elapsed")
  6. # back to WSGI callable
  7. me.switch()
  8.  
  9. def application(environ, start_response):
  10. start_response('200 OK', [('Content-Type','text/html')])
  11. myself = greenlet.getcurrent()
  12. asyncio.get_event_loop().call_later(2, two_seconds_elapsed, myself)
  13. # back to event loop
  14. myself.parent.switch()
  15. return [b"Hello World"]

我们可以更进一步,为WSGI生成器尽情使用uWSGI支持:

  1. import asyncio
  2. import greenlet
  3.  
  4. def two_seconds_elapsed(me):
  5. print("Hello 2 seconds elapsed")
  6. me.switch()
  7.  
  8. def application(environ, start_response):
  9. start_response('200 OK', [('Content-Type','text/html')])
  10. myself = greenlet.getcurrent()
  11. asyncio.get_event_loop().call_later(2, two_seconds_elapsed, myself)
  12. myself.parent.switch()
  13. yield b"One"
  14. asyncio.get_event_loop().call_later(2, two_seconds_elapsed, myself)
  15. myself.parent.switch()
  16. yield b"Two"

另一个例子:Future与协程

你可以使用 asyncio.Task 从你的 WSGI callable中生成协程:

  1. import asyncio
  2. import greenlet
  3.  
  4. @asyncio.coroutine
  5. def sleeping(me):
  6. yield from asyncio.sleep(2)
  7. # back to callable
  8. me.switch()
  9.  
  10. def application(environ, start_response):
  11. start_response('200 OK', [('Content-Type','text/html')])
  12. myself = greenlet.getcurrent()
  13. # enqueue the coroutine
  14. asyncio.Task(sleeping(myself))
  15. # suspend to event loop
  16. myself.parent.switch()
  17. # back from event loop
  18. return [b"Hello World"]

有了Future,我们甚至可以从协程中获取结果……

  1. import asyncio
  2. import greenlet
  3.  
  4. @asyncio.coroutine
  5. def sleeping(me, f):
  6. yield from asyncio.sleep(2)
  7. f.set_result(b"Hello World")
  8. # back to callable
  9. me.switch()
  10.  
  11.  
  12. def application(environ, start_response):
  13. start_response('200 OK', [('Content-Type','text/html')])
  14. myself = greenlet.getcurrent()
  15. future = asyncio.Future()
  16. # enqueue the coroutine with a Future
  17. asyncio.Task(sleeping(myself, future))
  18. # suspend to event loop
  19. myself.parent.switch()
  20. # back from event loop
  21. return [future.result()]

一个更高级的使用 aiohttp 模块的例子 (记住执行 pip install aiohttp 来安装它,它并不是一个标准库模块)

  1. import asyncio
  2. import greenlet
  3. import aiohttp
  4.  
  5. @asyncio.coroutine
  6. def sleeping(me, f):
  7. yield from asyncio.sleep(2)
  8. response = yield from aiohttp.request('GET', 'http://python.org')
  9. body = yield from response.read_and_close()
  10. # body is a byterray !
  11. f.set_result(body)
  12. me.switch()
  13.  
  14.  
  15. def application(environ, start_response):
  16. start_response('200 OK', [('Content-Type','text/html')])
  17. myself = greenlet.getcurrent()
  18. future = asyncio.Future()
  19. asyncio.Task(sleeping(myself, future))
  20. myself.parent.switch()
  21. # this time we use yield, just for fun...
  22. yield bytes(future.result())

状态

  • 该插件被认为是实验性的 (WSGI中使用asyncio的影响目前尚未清楚)。未来,当检测到Python >= 3.4的时候,可能会默认构建。
  • 虽然(或多或少)技术上是可行的,但是在不久的将来,并不期望将一个WSGI callable映射到一个Python 3协程上。
  • 该插件为非阻塞的读/写和定时器注册钩子。这意味着,你可以自动使用uWSGI API和asyncio。看看 https://github.com/unbit/uwsgi/blob/master/tests/websockets_chat_asyncio.py 这个例子。