异步响应(通过 Deferred)¶
前面的示例中有一个 Resource
,它异步生成响应,而不是在调用其 render 方法时立即生成。虽然它很好地演示了 Twisted Web 的 NOT_DONE_YET
功能,但该示例并未反映实际应用程序可能想要执行的操作。本示例介绍了 Deferred
,Twisted 类,用于为许多异步事件提供统一的接口,并展示了使用返回 Deferred
的 API 在 Twisted Web 中为请求生成异步响应的示例。
Deferred
是异步编程方法的两个结果。首先,异步代码经常(如果不是总是)涉及一些尚不可用但可能很快就会可用的数据(在 Python 中,是一个对象)。异步代码需要一种方法来定义一旦对象存在将对其执行的操作。它还需要一种方法来定义如何处理该对象创建或获取过程中的错误。这两个需求由 Deferred
的回调和错误回调来满足。回调使用 Deferred.addCallback
添加到 Deferred
中;错误回调使用 Deferred.addErrback
添加。当对象最终存在时,它将被传递给 Deferred.callback
,它将对象传递给使用 addCallback
添加的回调。类似地,如果发生错误,则调用 Deferred.errback
,并将错误传递给使用 addErrback
添加的错误回调。其次,使异步代码真正起作用的事件通常采用许多不同的、不兼容的形式。 Deferred
充当统一接口,使异步应用程序的不同部分能够交互,并将其与它们不应该关注的实现细节隔离开来。
这几乎就是 Deferred
的全部内容。为了巩固您对新知识的理解,现在考虑这个 DelayedResource
的重写版本,它使用基于 Deferred
的延迟 API。它与 前面的示例 做完全相同的事情。只有实现不同。
首先,该示例必须导入刚刚提到的新 API,deferLater
from twisted.internet.task import deferLater
接下来,所有其他导入(这些与上次相同)
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
from twisted.internet import reactor
完成导入后,以下是 DelayedResource
实现的第一部分。同样,代码的这一部分与之前的版本相同
class DelayedResource(Resource):
def _delayedRender(self, request):
request.write(b"<html><body>Sorry to keep you waiting.</body></html>")
request.finish()
接下来,我们需要定义 render 方法。这里有一些变化。我们不会使用 callLater
,而是使用 deferLater
。 deferLater
接受一个 reactor、延迟(以秒为单位,与 callLater
相同)以及一个函数,该函数在延迟后调用以生成 Deferred
描述中提到的那个难以捉摸的对象。我们还将使用 _delayedRender
作为要添加到 deferLater
返回的 Deferred
的回调。由于它期望请求对象作为参数,因此我们将设置 deferLater
调用以返回一个 Deferred
,该 Deferred
的结果是请求对象。
...
def render_GET(self, request):
d = deferLater(reactor, 5, lambda: request)
现在,由 d
引用的 Deferred
需要添加 _delayedRender
回调。完成此操作后,将使用 d
的结果(当然,将是 request
— (lambda: request)()
的结果)调用 _delayedRender
。
...
d.addCallback(self._delayedRender)
最后,render 方法仍然需要返回 NOT_DONE_YET
,原因与之前示例版本中的原因完全相同。
...
return NOT_DONE_YET
就这样,DelayedResource
现在基于 Deferred
实现。该示例仍然不太现实,但请记住,由于 Deferred
为许多不同的异步事件源提供统一的接口,因此这段代码现在更像一个真实的应用程序;您可以轻松地用另一个返回 Deferred
的 API 替换 deferLater
,突然之间,您可能就拥有了一个可以执行有用操作的资源。
最后,以下是完整的、未中断的示例源代码,作为 rpy 脚本
from twisted.internet.task import deferLater
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
from twisted.internet import reactor
class DelayedResource(Resource):
def _delayedRender(self, request):
request.write(b"<html><body>Sorry to keep you waiting.</body></html>")
request.finish()
def render_GET(self, request):
d = deferLater(reactor, 5, lambda: request)
d.addCallback(self._delayedRender)
return NOT_DONE_YET
resource = DelayedResource()