作者:dave@http://krondo.com/deferred-poetry/  译者:杨晓伟(采用意译)

可以从这里从头开始阅读这个系列。

客户端4.0

我们已经对deferreds有些理解了,现在我们可以使用它重写我们的客户端。你可以在twisted-client-4/get-poetry.py看到它的实现。

这里的get_poetry已经再也不需要callback与errback参数了。相反,返回了一个用户可能根据需要添加callbacks和errbacks的新deferred。

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的被激活。


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已经成为惯例。

讨论

新版本的客户端与我们前面的同步版本的客户端一样,get_poetry得到的参数都是诗歌下载服务器的地址。同步版本返回的是诗歌内容,而异步版本返回的却是一个deferred。返回一个deferred是Twisted的APIs或用Twisted写的程序常见的,这样一来我们可以这样来理解deferred:

一个Deferred代表了一个“异步的结果”或者“结果还没有到来”

在图13中可以更加清晰地表达出两者之间的不同:

图13:同步 VS 异步

异步函数返回一个deferred,对用户意味着:

我是一个异步函数。不管你想要什么,可能现在马上都得不到。但当结果来到时,我会激活这个deferredcallback链并返回结果。或者当出错时,相应地激活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部分探讨其更多的功能和功能背后的动机。

Python Twisted系列教程8:使用Deferred的诗歌下载客户端的更多相关文章

  1. Python Twisted系列教程10:增强defer功能的客户端

    作者:dave@http://krondo.com/an-introduction-to-asynchronous-programming-and-twisted/ 译者:杨晓伟(采用意译) 可以从这 ...

  2. Python Twisted系列教程14:Deferred用于同步环境

    作者:dave@http://krondo.com/when-a-deferred-isnt/  译者:杨晓伟(采用意译) 你可以从这里从头开始阅读这个系列. 介绍 这部分我们要介绍Deferred的 ...

  3. Python Twisted系列教程6:抽象地利用Twisted

    作者:dave@http://krondo.com/and-then-we-took-it-higher/  译者:杨晓伟(采用意译) 你可以从这里从头开始阅读这个系列. 打造可以复用的诗歌下载客户端 ...

  4. Python Twisted系列教程9:第二个小插曲,Deferred

    作者:dave@http://krondo.com/a-second-interlude-deferred/ 译者:杨晓伟(采用意译) 可以从这里从头来阅读这个系列 更多关于回调的知识 稍微停下来再思 ...

  5. Python Twisted系列教程12:改进诗歌下载服务器

    作者:dave@http://krondo.com/a-poetry-transformation-server/  译者:杨晓伟(采用意译) 你可以从这里从头阅读这个系列. 新的服务器实现 这里我们 ...

  6. Python Twisted系列教程5:由Twisted支持的诗歌客户端

    作者:dave@http://krondo.com/twistier-poetry/  译者:杨晓伟(采用意译) 你可以从这里从头开始阅读这个系列 抽象地构建客户端 在第四部分中,我们构建了第一个使用 ...

  7. Python Twisted系列教程11:改进诗歌下载服务器

    作者:dave@http://krondo.com/your-poetry-is-served/ 译者:杨晓伟(采用意译) 你可以从这里从头阅读这个系列. 诗歌下载服务器 到目前为止,我们已经学习了大 ...

  8. Python Twisted系列教程7:小插曲,Deferred

    作者:dave@http://krondo.com/an-interlude-deferred/  译者:杨晓伟(采用意译) 你可以从这里从头开始阅读这个系列 回调函数的后序发展 在第六部分我们认识这 ...

  9. Python Twisted系列教程20: Twisted和Erlang

    作者:dave@http://krondo.com/twisted-and-erlang/  译者: Cheng Luo 你可以从”第一部分 Twist理论基础“开始阅读:也可以从”Twisted 入 ...

随机推荐

  1. C# 运行时替换方法(需要unsafe编译)

    /* https://stackoverflow.com/questions/7299097/dynamically-replace-the-contents-of-a-c-sharp-method ...

  2. SpringCloud分布式开发理解

    谈到SpringCloud最新接触到的可能就是那五大"神兽",之前最先接触分布式开发是通过dubbo的RPC远程过程调用,而dubbo给我得感觉就是:虽然所有的主机物理上分布了,但 ...

  3. mongodb之一些简单的增删改查语句

    数据库操作:show dbs;#查看数据库use test;#如果没有就创建一个db;#查看当前数据库db.dropDatabase();#删除数据库 数据操作:show collections:#查 ...

  4. element-ui 的el-button组件中添加自定义颜色和图标的实现方法

    这篇文章主要介绍了element-ui 的el-button组件中添加自定义颜色和图标的实现方法,目前的解决方案是:添加一个自定义全局指令,同时在element-ui源码中,加入对应的组件.需要的朋友 ...

  5. Easyui 行编辑

    之前没用过,突然用了的时候手忙脚乱的感觉  找了官方文档也好 百度了一大堆东西   表示个人脑袋跟不上思路 直接铺上简化的  以后自己 找起来也方便  以下代码已经执行 应该不会再错了 <tab ...

  6. 【转】Symstore 详细使用

    SymStore (symstore.exe) 是用于创建符号存储的工具.它被包含在Windows调试工具包中. SymStore按照某种格式存储符号,使得调试器可以通过时间戳.映像大小(对于.dbg ...

  7. C#实现文件与二进制互转并存入数据库

    这篇文章主要介绍了C#实现文件与二进制互转并存入数据库,本文直接给出代码实例,代码中包含详细注释,需要的朋友可以参考下 //这个方法是浏览文件对象     private void button1_C ...

  8. 使用 DocFX 生成 .Net/Unity项目文档

    孙广东  2017.5.27 http://blog.csdn.NET/u010019717 微软开源全新的文档生成工具DocFX   类似JSDoc或Sphinx     如何使用看 : http: ...

  9. js 获取服务器当前时间

    //获取服务器时间 function getNowDate(){ var xhr = null; if(window.XMLHttpRequest){ xhr = new window.XMLHttp ...

  10. Jar包进行反编译,修改后重新打包

    在学习和开发JAVA项目中,我们经常会用到第三方提供的一些jar.使用这些第三方工具包,可以提高我们开发的效率,缩短开发的时间.有的第三方工具,提供具体的使用说明和源代码,有时有的却不提供源代码,使用 ...