Part 13: Deferred All The Way Down
原文:http://krondo.com/blog/?p=2159
作者:dave
译者:notedit
时间:2011.06.26
Introduction
回想一下第十部分的poetry client 5.1,client 用一个deferred 来管理一个callback 链,这个callback 链中调用了一个transformation 引擎,在client 5.1 中,这个引擎是作为一个同步的函数来实现的.
现在我们想写一个新的client,让它利用我们在第十二部分写的transformation service.问题就来了:既然transformation service 是通过网络访问的,我们需要用异步的I/O.这就意味着我们用来请求transformation 的api 是异步的.也就是说我们的try_to_cummingsify callback 会返回一个Deferred 对象
在一个deferred 链中的callback 返回另一个deferred 的时候会发生什么?让我们把第一个deferred 叫做外部的deferred 第二个deferred 叫做内部的deferred.假设在外部的deferred 中callback N 返回了 内部的deferred.这个callback 是在说”我是异步的,我的结果还没有来”.因为外部的deferred 需要调用下一个callback 或者errback,并传递当前callback 的返回值,外部的deferred 需要等待内部的deferred被触发.当然,外部的deferred 也不能是阻塞的,所以,此时外部的deferred暂定callback 链的执行并把控制圈交还给reactor.
外部的deferred 是怎样知道什么时候恢复呢? 很简单,通过给内部的deferred增加一个callback/errback 对.当内部的deferred 被触发的时候,外部的deferred 会恢复执行.假如内部的deferred成功了(例如:它调用了一个被外部的deferred增加的callback),外部的deferred则会继续调用它的N+1callback,假如内部的deferred 失败了,外部的deferred 会调用它的 N+1 errback.
让我们用一张图片来描述这个过程,图片二十八:

在这张图片中,外部的deferred 有四对callback/errback.当外部的deferred 被触发时,第一个callback 返回了一个deferred(内部的deferred).这时候外部的deferred会停止触发它的callback 链,并把控制权交给reactor(在给内部的deferred 的增加了一对callback/errback 之后).然后,一段时间之后,内部的deferred触发,外部的deferred 也开始恢复运行.注意,外部的deferred 并不会自己触发内部的deferred.那也是不可能的,因为外部的deferred 不会知道什么时候内部的deferred的结果是可用的,或者结果是什么.我们的外部的deferred就是异步的等待内部的deferred 触发.
注意连接callback 和内部的deferred 的那跟线是黑色的而不是绿色或红色.那是因为我们我不知这个callback 是成功还是失败知道内部的deferred触发.直到那时候外部的deferred 才能知道是去调用下一个callback 还是下一个errback.
图片二十九描述了相同的外部的/内部的deferred 触发顺序,不过是站在reactor 的角度:

这个可能是deferred 最难懂的部分,如果你在短时间内不能消化也不要着急.我们会用具体的程序举例说明–twisted-deferred/defer-10.py.这个例子创造了两个外部的deferred,一个带有空白的callbacks,另一个有一个callback 并返回一个内部的deferred.通过学习这个例子你可以搞明白第二个外部的deferred是怎样在内部的deferred 返回的时候停止的,和 在内部的deferred被触发的时候外部的deferred 又是怎样恢复的.
Client 6.0
让我们用我们新学的嵌套的deferred 来重新实现一下我们的poetry client,并用上第十二部分讲到的transformation service,你可以在twisted-client-6/get-poetry.py 找到代码. poetry protocol 和protocol factory 和前一版本的client 都没有变化.但是增加了进行transformation 请求的protocol 和factory.下面是protocol 部分:
class TransformClientProtocol(NetstringReceiver):
def connectionMade(self):
self.sendRequest(self.factory.xform_name,
self.factory.poem)
def sendRequest(self, xform_name, poem):
self.sendString(xform_name + '.' + poem)
def stringReceived(self, s):
self.transport.loseConnection()
self.poemReceived(s)
def poemReceived(self, poem):
self.factory.handlePoem(poem)
使用NetstringReceiver 作为一个基类让这个protocol 相当的简单.只要连接一建立,我们从factory 中取到变形的名字和诗的内容并向server发送一个transform 请求.当我们得到返回的诗,我们把它传递给factory,下面是factory 的代码:
class TransformClientFactory(ClientFactory):
protocol = TransformClientProtocol
def __init__(self, xform_name, poem):
self.xform_name = xform_name
self.poem = poem
self.deferred = defer.Deferred()
def handlePoem(self, poem):
d, self.deferred = self.deferred, None
d.callback(poem)
def clientConnectionLost(self, _, reason):
if self.deferred is not None:
d, self.deferred = self.deferred, None
d.errback(reason)
clientConnectionFailed = clientConnectionLost
这个factory 是被client 设计的,处理一个transformation 请求,并存储着transform 的名字和这首诗的内容.factory 创造了一个代表了这个transformation请求返回结果的deferred.注意这个factory 是怎样处理两种错误情况的:一个是连接错误的情况一个是还没完全接受到返回值的时候连接就断开的情况.也注意clientConnectionLost 方法就算我们接受诗成功最后也会调用,但是在这种情况下self.deferred 已经被handlepoem 设置为None了.
这个factory 创建了一个deferred 也触发了它,这是一个很好的方法:
一般来说,一个创造了deferred 的对象,也应该负责触发那个deferred
这个”你创造它,你触发它”规则帮助我们保证一个deferred 仅仅被触发一次,也让程序流程更简单一些.
除了这个transform Factory,这里还有一个proxy 类,它隐藏连接transform server 的具体信息:
class TransformProxy(object):
"""
I proxy requests to a transformation service.
"""
def __init__(self, host, port):
self.host = host
self.port = port
def xform(self, xform_name, poem):
factory = TransformClientFactory(xform_name, poem)
from twisted.internet import reactor
reactor.connectTCP(self.host, self.port, factory)
return factory.deferred
这个类提供了一个xform()接口,其他的代码可以用它来发送transformations 请求.所以其他的代码就可以仅仅发送一个transformations 请求然后得到一个deferred,而不用再去关心ip 和端口号.
其他的代码变化的地方还有 try_to_cummingsify callback:
def try_to_cummingsify(poem):
d = proxy.xform('cummingsify', poem)
def fail(err):
print >>sys.stderr, 'Cummingsify failed!'
return poem
return d.addErrback(fail)
这个callback 现在返回一个deferred,但是我们不用改变其他的main 函数中的代码.因为try_to_cummingsify 本来就在deferred 的链中,它已经是异步的了,其他的就不用变化了.
你可能会发现我们返回的是d.addErrback(fail) 的结果,这里是用了一些语法糖.addCallback 和 addErrback 都返回原来的deferred.我们也可以写成:
d.addErrback(fail) return d
Testing out the Client
这个新版的client 和其他的client相比有一些语法上的变化,假如你有一个transformation service 运行在10001 端口,两个poetry server 运行在10002 和 10003 上,你应该这样启动client:
python twisted-client-6/get-poetry.py 10001 10002 10003
你可以这样启动transformation service :
python twisted-server-1/transformedpoetry.py --port 10001
这样启动poetry server:
python twisted-server-1/fastpoetry.py --port 10002 poetry/fascination.txt
python twisted-server-1/fastpoetry.py --port 10003 poetry/science.txt
Wrapping Up
在这一部分我们学习了在一个callback 链中一个deferred 怎样透明的处理其他的deferred.我们可以安全的增加异步的callback到一个外部的deferred 中.这个是非常有用的因为我们的很多函数都要求是异步的.
我们知道deferred 的全部的事情了么? 还没有.还有很重要的一点,我们会在第十四部分讲到.




















