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’构建配置文件。
- CFLAGS="-I/usr/local/include/python3.4" make PYTHON=python3.4 asyncio
或者
- 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秒后触发一个函数(神奇!)。
- import asyncio
- def two_seconds_elapsed():
- print("Hello 2 seconds elapsed")
- def application(environ, start_response):
- start_response('200 OK', [('Content-Type','text/html')])
- asyncio.get_event_loop().call_later(2, two_seconds_elapsed)
- return [b"Hello World"]
一旦被调用,那么该应用函数将在asyncio事件循环中注册一个 callable,然后会返回到客户端。
在2秒后,事件循环将会运行该函数。
你可以这样运行这个例子:
- uwsgi --asyncio 10 --http-socket :9090 --greenlet --wsgi-file app.py
—asyncio
是一个快捷方式,它启用10个uWSGI异步核,让你能够用一个单一的进程就可以管理多达10个并发请求。
但是,如何在WSGI callable中等待一个回调完成呢?我们可以使用greenlet来挂起我们的WSGI函数 (记住,我们的WSGI callable是封装在一个greenlet中的):
- import asyncio
- import greenlet
- def two_seconds_elapsed(me):
- print("Hello 2 seconds elapsed")
- # back to WSGI callable
- me.switch()
- def application(environ, start_response):
- start_response('200 OK', [('Content-Type','text/html')])
- myself = greenlet.getcurrent()
- asyncio.get_event_loop().call_later(2, two_seconds_elapsed, myself)
- # back to event loop
- myself.parent.switch()
- return [b"Hello World"]
我们可以更进一步,为WSGI生成器尽情使用uWSGI支持:
- import asyncio
- import greenlet
- def two_seconds_elapsed(me):
- print("Hello 2 seconds elapsed")
- me.switch()
- def application(environ, start_response):
- start_response('200 OK', [('Content-Type','text/html')])
- myself = greenlet.getcurrent()
- asyncio.get_event_loop().call_later(2, two_seconds_elapsed, myself)
- myself.parent.switch()
- yield b"One"
- asyncio.get_event_loop().call_later(2, two_seconds_elapsed, myself)
- myself.parent.switch()
- yield b"Two"
另一个例子:Future与协程
你可以使用 asyncio.Task
从你的 WSGI callable中生成协程:
- import asyncio
- import greenlet
- @asyncio.coroutine
- def sleeping(me):
- yield from asyncio.sleep(2)
- # back to callable
- me.switch()
- def application(environ, start_response):
- start_response('200 OK', [('Content-Type','text/html')])
- myself = greenlet.getcurrent()
- # enqueue the coroutine
- asyncio.Task(sleeping(myself))
- # suspend to event loop
- myself.parent.switch()
- # back from event loop
- return [b"Hello World"]
有了Future,我们甚至可以从协程中获取结果……
- import asyncio
- import greenlet
- @asyncio.coroutine
- def sleeping(me, f):
- yield from asyncio.sleep(2)
- f.set_result(b"Hello World")
- # back to callable
- me.switch()
- def application(environ, start_response):
- start_response('200 OK', [('Content-Type','text/html')])
- myself = greenlet.getcurrent()
- future = asyncio.Future()
- # enqueue the coroutine with a Future
- asyncio.Task(sleeping(myself, future))
- # suspend to event loop
- myself.parent.switch()
- # back from event loop
- return [future.result()]
一个更高级的使用 aiohttp
模块的例子 (记住执行 pip install aiohttp
来安装它,它并不是一个标准库模块)
- import asyncio
- import greenlet
- import aiohttp
- @asyncio.coroutine
- def sleeping(me, f):
- yield from asyncio.sleep(2)
- response = yield from aiohttp.request('GET', 'http://python.org')
- body = yield from response.read_and_close()
- # body is a byterray !
- f.set_result(body)
- me.switch()
- def application(environ, start_response):
- start_response('200 OK', [('Content-Type','text/html')])
- myself = greenlet.getcurrent()
- future = asyncio.Future()
- asyncio.Task(sleeping(myself, future))
- myself.parent.switch()
- # this time we use yield, just for fun...
- 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 这个例子。