客户端4.0python
咱们已经对deferreds有些理解了,如今咱们可使用它重写咱们的客户端。你能够在twisted-client-4/get-poetry.py中看到它的实现。react
这里的get_poetry已经不再须要callback与errback参数了。相反,返回了一个用户可能根据须要添加callbacks和errbacks的新deferred。git
def get_poetry(host, port): """ Download a poem from the given host and port. This function returns a Deferred which will be fired with the complete text of the poem or a Failure if the poem could not be downloaded. """ d = defer.Deferred() from twisted.internet import reactor factory = PoetryClientFactory(d) reactor.connectTCP(host, port, factory) return d
这里的工厂使用一个deferred而不callback/errback对来初始化。一旦咱们获取到poem后或者没有链接到服务器上,deferred就会以返回一首诗歌或一个failure的被激活。github
class PoetryClientFactory(ClientFactory): protocol = PoetryProtocol def __init__(self, deferred): self.deferred = deferred def poem_finished(self, poem): if self.deferred is not None: d, self.deferred = self.deferred, None d.callback(poem) def clientConnectionFailed(self, connector, reason): if self.deferred is not None: d, self.deferred = self.deferred, None d.errback(reason)
注意咱们在deferred被激活后是如何销毁其引用的。这种方式普便存在于Twisted的源代码中,这样作能够保证咱们不会激活一个deferred两次。这也为Python的垃圾回收带来的方便。编程
这里仍然不用去改变poetryProtocol。咱们只须要更新poetry_main函数便可:服务器
def poetry_main(): addresses = parse_args() from twisted.internet import reactor poems = [] errors = [] def got_poem(poem): poems.append(poem) def poem_failed(err): print >>sys.stderr, 'Poem failed:', err errors.append(err) def poem_done(_): if len(poems) + len(errors) == len(addresses): reactor.stop() for address in addresses: host, port = address d = get_poetry(host, port) d.addCallbacks(got_poem, poem_failed) d.addBoth(poem_done) reactor.run() for poem in poems: print poem
注意咱们是如何利用deferred的回调链在不考虑两个主要的callback与errback回调外,重构poem_done调用的。架构
因为deferred在Twisted大量被使用,使用小写字母d来表示当前正在工做中的deferred已经成为惯例。app
讨论异步
新版本的客户端与咱们前面的同步版本的客户端同样,get_poetry获得的参数都是诗歌下载服务器的地址。同步版本返回的是诗歌内容,而异步版本返回的倒是一个deferred。返回一个deferred是Twisted的APIs或用Twisted写的程序常见的,这样一来咱们能够这样来理解deferred:异步编程
一个Deferred表明了一个“异步的结果”或者“结果尚未到来”
在图13中能够更加清晰地表达出二者之间的不一样:
图13:同步 VS 异步
异步函数返回一个deferred,对用户意味着:
我是一个异步函数。无论你想要什么,可能如今立刻都得不到。但当结果来到时,我会激活这个deferred的callback链并返回结果。或者当出错时,相应地激活errback链并返回出错信息。
固然,这个函数是不能随意激活这个deferred的,由于它已经返回了。但这个函数已经启动了一系列事件,这些事件最终将会激活这个deferred。
所以,deferred是为适应异步模式的一种延迟函数返回的方式。函数返回一个deferred意味着其是异步的,表明着未来的结果,也是对未来可以返回结果的一种承诺。
同步函数也能返回一个deferred,所以严格点说,返回deferred只说多是异步的。咱们会在未来的例子中看到同步函数返回deferred。
因为deferred的行为已经很好的定义与理解,所以在实现本身的API时返回一个deferred更容易让其它的Twisted程序理解你的代码。若是没有deferred,可能每一个人写的模块都使用不一样的方式来处理回调。这要一来就增长了相互理解的工做量。
当你使用Deferred时,你仍然在使用回调,它们仍然由reactor来调用。
当首次学习Twisted时,常常犯的一个错误就是:会给deferred增长一些它自己不能实现的功能。尤为是:常常假设在deferred上添加一个函数就可使其变成异步函数。这可能会让你产生这样的想法:在Twisted 中能够经过将os.system的函数添加到deferred的回调链中。
我认为,这多是没有弄清楚异步编程的缘由才产生这样的想法。因为Twisted代码使用了大量的deferred但却不多会涉及到reactor,可能会认为deferred作了大部分工做。若是你是从开始阅读这个系列的,你就会知道事情远不是这样。虽然Twisted是由众多部分组合在一块儿来工做的,但实现异步的主要工做都是由reactor来完成的。Deferred是一个很好的抽象概念,但前面几个例子中的客户端咱们却没有使用它,而reactor却都用到了。
来看看咱们第一个回调激活时的跟踪栈信息。运行twisted-client-4/get-poetry-stack.py让其链接你打开的服务器:
File "twisted-client-4/get-poetry-stack.py", line 129, in poetry_main() File "twisted-client-4/get-poetry-stack.py", line 122, in poetry_main reactor.run() ... # some more Twisted function calls protocol.connectionLost(reason) File "twisted-client-4/get-poetry-stack.py", line 59, in connectionLost self.poemReceived(self.poem) File "twisted-client-4/get-poetry-stack.py", line 62, in poemReceived self.factory.poem_finished(poem) File "twisted-client-4/get-poetry-stack.py", line 75, in poem_finished d.callback(poem) # here's where we fire the deferred ... # some more methods on Deferreds File "twisted-client-4/get-poetry-stack.py", line 105, in got_poem traceback.print_stack()
这很像版本2.0的跟踪栈,图14能够很好地说明具体的调用关系:
图14 deferred的回调
这很相似于咱们前面的Twisted客户端,虽然这张图的调用关系并不清晰而会你摸不着头脑。但咱们先不深刻分析这张图。有一个细节并无在这张图上反映出来:callback链直到第二个回调poem_done激活前才将控制权还给reactor。
经过使用deferred,咱们在由Twisted中的reactor启动的回调中加入了一些本身的东西,但咱们并无改变异步程序的基础架构。回忆下回调编程的特色:
1.在一个时刻,只会有一个回调在运行
2.当reactor运行时,那咱们本身的代码则得不到运行
3,反之则反之
4.若是咱们的回调函数发生阻塞,那么整个程序就跟着阻塞掉了
在一个 deferred上追加一个回调并不会改变上面这些实事。尤为是,第4 条。所以当一个deferred激活时被阻塞,那么整个Twisted就会陷入阻塞中。所以咱们会获得以下结论:
Deferred只是解决回调函数管理问题的一种解决方案。它并不一种替代回调方式也不能将阻塞式的回调变成非阻塞式回调的。
我经过构建一个添加阻塞式回调的deferred来验证最后一点。验证代码文件为twisted-deferred/defer-block.py。第二个callback经过使用time.sleep来达到阻塞的效果。若是你运行该代码来观察打印信息顺序时,你会发现deferred中阻塞回调仍然会阻塞掉。
总结
函数经过返回一个Deferred,向使用者暗示“我是采用异步方式的”而且当结果到来时会使用一种特殊的机制(在此处添加你的callback与errback)来得到返回结果。Defered被普遍地运用在Twisted的每一个角落,当你浏览Twisted源码时你就会不停地遇到它。
4.0版本客户端是第一个使用Deferred的Twisted版的客户端,其使用方法为在其异步函数中返回一个deferred来。可使用一些Twisted的APIs来使客户端的实现更加清晰些,但我以为它可以很好地体现出一个简单的Twisted程序是怎么写的了,至少对于客户端能够如此确定。事实上,后面咱们会重构咱们的服务器端。
但咱们对Deferred的讲解尚未结束。使用如此少许的代码,Deferred就能提供如此之多的功能。咱们将在第9部分探讨其更多的功能和功能背后的动机。