介绍python
回忆下第10部分中的客户端5.1版。客户端使用一个Deferred来管理全部的回调链,其中包括一个格式转换引擎的调用。在那个版本中,这个引擎的实现是同步的。(即等待其执行再切到其它函数或任务中)react
如今咱们想实现一个新的客户端,其使用咱们在第十二部分实现的格式服务器提供的格式转换服务。但这里有一个问题须要说清楚:因为格式转换服务是经过网络获取的,所以咱们须要使用异步I/O。这也就意味着咱们获取格式转换服务的API必须是异步实现的。换句话说,try_to_cummingsify回调将会在新客户端中返回一个deferred。git
若是在一个deferred的回调链中的一个回函数又返回了一个 deferred会发生什么现象呢?咱们规定前一个deferred为外层deferred,然后者则为内层deferred。假设回调N在外层deferred中返回一个内层的deferred。意味着这个回调宣称“我是一个异步函数,结果不会当即出现!”。因为外层的deferred须要调用回调链中下一个callback或errback并将回调N的结果传下去,所以,其必须等待直到内层deferred被激活。固然了,外层的deferred不可能处于阻塞状态,由于控制权此时已经转交给了reactor而且阻塞了。github
那么外层的deferred如何知晓什么时候恢复执行呢?很简单,在内层deferred上添加callback或errback便可(即激活内层的deferred)。所以,当内层deferrd被激活时,外层的deferred恢复其回调链的执行。当内层deferred回调执行成功,那么外层deferred会调用第N+1个callback回调。相反,若是内层deferred执行失败,那么外层deferred会调用第N+1个errback回调。编程
图28形象地解释说明了这一过程:服务器
图28 内层与外层deferred的交互网络
在这个图示中,外层的deferred有四个callback/errback对。当外围的deferred被激活后,其第一个callback回调返回了一个deferred(即内层deferred)。从这里开始,外层的deferred中止激活其回调链而且将控制权交还给了reactor(固然是在给内层deferred添加callback/errback以后)。过了一段时间以后,内层deferred被激活,而后执行它的回调链并执行完毕后恢复外层deferred的回调执行过程。注意到,外层deferred是没法激活内层deferred的。这是不可能的,由于外层的deferred根本就没法获知内层的deferred什么时候能把结果准备好及结果内容是什么。相反,外层的deferred只可能等待(固然是异步方式)内部deferred的激活。异步
注意到外层deferred的产生内层deferred的回调的连线是黑色的而不是红色或蓝色,这是由于咱们在内层deferred激活以前是没法获知此回调返回的结果是执行成功还执行失败。只有在内层deferred激活时,咱们才能决定下一个回调是callback仍是errback。函数
图29从reactor的角度来讲明了外层与内层deferred的执行序列:学习
图29 控制权的转换
这也许是Deferred类最为复杂的功能,但无需担忧你可能会花费大量时间来理解它。咱们将在示例twisted-deferred/defer-10.py中说明如何使用它。这个例子中,咱们建立了两个外层deferred,一个使用了简单的回调,另外一个其中的一个回调返回了一个内部deferred。经过阅读这段代码,咱们能够发现外层deferred是在内层deferred激活后才开始继续执行回调链的。
客户端版本6.0
咱们将使用新学的deferred嵌套来重写咱们的客户端来使用由服务器提供的样式转换服务。其实现代码在
twisted-client-6/get-poetry.py中。与前几个版本同样,协议与工厂都没有改变。但咱们添加了进行格式转换服务请求的协议与工厂实现。下面是协议实现代码:
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做为基类能够很简单地实现咱们的协议。只要链接一旦创建咱们就发出格式转换服务的请求。当咱们获得格式转换以后的诗歌后交给工厂进行处理,下面是工厂代码:
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
值得注意的是,工厂是如何处理两种类型错误:链接失败与在诗歌未所有接收完就中断链接。而且clientConncetionLost可能会在咱们已经接收完诗歌后激活执行(即链接断开了),但在这种状况下,self.deferred已是个None值,这利益于handePoem中对deferredr 处理。
这个工厂建立了一个deferred而且最后激活了它,这在Twisted编程中是一个好的习惯,即
一般状况下,一个对象建立了一个deferred,那么它应当负责激活它。
除了格式转换工厂外,还有一个Proxy类开包装了具体建立一个TCP链接到格式转换服务器:
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接口,以让其它程序请求格式转换服务。这样一来其它代码只须要提出请求并获得一个deferred,而无需考虑什么端口与IP地址之类的问题。
剩下的代码除了try_to_cummingsify外都没有改变:
def try_to_cummingsify(poem): d = proxy.xform('cummingsify', poem) def fail(err): print >>sys.stderr, 'Cummingsify failed!' return poem return d.addErrback(fail)
这个做为外层deferred的回调返回了一个内层的deferred,但咱们仍然须要更改main方法,除了建立了一个Proxy对象。因为try_to_cummingsify已是deferred回调链中的一部分,所以其早已使用了异步方式。所以咱们说main函数无需更改。
你可能注意到return d.addErrback(fail)这句,其它它等于
d.addErrback(fail) return d
结束语
这一部分咱们学习了关于deferred如何透明地完成了回调链内部再次处理deferred。并由此,咱们能够无需考虑内部实现细节并放心地在外部deferred上添加回调。
在第十四部分,咱们将讲解deferred的另一个特性。