文档章节

(转) Twisted : 第九部分 第二个小插曲,Deferred

水果糖
 水果糖
发布于 2016/01/27 13:22
字数 3632
阅读 8
收藏 0
点赞 1
评论 0

更多关于回调的知识

稍微停下来再思考一下回调的机制。尽管对于以Twisted方式使用Deferred写一个简单的异步程序已经非常了解了,但Deferred提供更多的是只有在比较复杂环境下才会用到的功能。因此,下面我们自己想出一些复杂的环境,以此来观察当使用回调编程时会遇到哪些问题。然后,再来看看deferred是如何解决这些问题的。

因此,我们为诗歌下载客户端添加了一个假想的功能。设想一些计算机科学家发明了一种新诗歌关联算法,

Byronification引擎。这个漂亮的算法根据一首诗歌生成一首使用Lord Byron式的同样的诗歌。另外,专家们提供了其Python的接口,即:

class IByronificationEngine(Interface): 
    def byronificate(poem):
        """
        Return a new poem like the original, but in the style of Lord Byron.
        Raises GibberishError if the input is not a genuine poem.
        """


像大多数高尖端的软件一样,其实现都存在着许多bugs。这意外着除了已知的异常外,这个byronificate 方法可能会抛出一些专家当时没有预料到的异常出来。

我们还可以假设这个引擎能够非常快的动作以至于我们可以在主线程中调用到而无需考虑使用reactor。下面是我们想让程序实现的效果:

1.尝试下载诗歌

2.如果下载失败,告诉用户没有得到诗歌

3.如果下载到诗歌,则转交给Byronificate处理引擎一份

4.如果引擎抛出GibberishError,告诉用户没有得到诗歌

5.如果引擎抛出其它异常,则将原始式样的诗歌立给用户

6.如果我们得到这首诗歌,则打印它

7.结束程序

这里设计是当遇到GibberishError异常则表示没有得到诗歌,因此我们直接告诉用户下载失败即可。这也许对调试没什么用处,但我们的用户关心的只是我们下载到诗歌没有。另一方面,如果引擎因为一些其它的原因而出现处理失败,那么我们将原始诗歌交给用户。毕竟,有诗歌呈现总比没有好,虽然不是用户想要的Byron样式。


下面是同步模式的代码:

try:
    poem = get_poetry(host, port) # synchronous get_poetry
except:
    print >>sys.stderr, 'The poem download failed.'
else:
    try:
        poem = engine.byronificate(poem)
    except GibberishError:
        print >>sys.stderr, 'The poem download failed.'
    except:
        print poem # handle other exceptions by using the original poem
    else:
        print poem
sys.exit()


这段代码可能经过一些重构会更加简单,但已经足以说明上面的逻辑流程。我们想升级那些最近使用deferred的客户端来使用这个功能。但这部分内容我准备把它放在第十部分。现在,我们来考虑一下,用版本3.1来实现这个功能,最后一个没有使用deferred的客户端。假设我们无需考虑处理异常,那么只是改变一下got_poem回调即可:

def got_poem(poem):
    poems.append(byron_engine.byronificate(poem))
    poem_done()


那么如果byronificate抛出GibberishError异常或其它异常会发生什么呢?看看第六部分的图11,我们可以得到:

1.这个异常会传播到工厂中的poem_finished回调,即激活got_poem的方法

2.由于poem_finished并没有捕获这个异常,因此其会传递到protocol中的poemReceive函数

3.然后来到connectionLost函数,仍然在protocol

4.然后就来到Twisted的核心区,最后止步于reactor

前面已经了解到,reactor会捕获异常并记录它而不是“崩溃”掉。但它却不会告诉用户我们的诗歌下载失败的消息。reactor并不知道任何诗歌或GibberishErrors的信息,它只是一段被设计成适应所有网络类型的通用代码,即便与诗歌无关的网络服务。(Dave这里想强调的是reactor只是做一些具有普遍意义的事情,不会单独去处理特定的问题,例如这里原GibberishErrors异常)

注意异常是如何顺着调用链传递到具有通用性代码区域。并且看到,在got_poem后面任何一步都没有可望以我们客户端的具体要求来处理异常的。这与同步代码中的方式恰恰相反。

15揭示了一个同步客户端的调用栈:


第九部分:第二个小插曲,Deferred

15:同步调用栈

main函数是最高层,意味着它可以触及整个程序,它为什么要存在,并且它是如何在整体上表现的。典型的,main函数可以触及到用户在命令行输入想让程序做什么的参数。并且它还有一个特殊的目的:为一个命令行式的客户端打印结果。

socketconnet函数,恰恰相反,其为最低层。它所知道的就是提供到指定地址的连接。它并不知道另一端是什么及我们为什么要进行连接。但connect有通用性,不管你因为何种服务要进行网络连接都可以使用它。

get_poetry在中间,它知道要取一些诗歌,但并不知道如果得不到诗歌会发生什么。因此,从connect抛出的异常会向上传递,从低层的具有通用性的代码区到高层的具有针对性的代码区,直到其传递到知道如何处理这个异常的代码区。

现在,我们再回来看看对3.1版的假想功能的实现。我们在图16里对调用栈进行了分析,当然只是说明了其中关键的函数:



第九部分:第二个小插曲,Deferred

16 异步调用栈

现在问题非常清晰了:在回调中,低层的代码(reactor)调用高层的代码,其甚至还会调用更高层的代码。因此一旦出现了异常,它并不会立即被其附件(在调用栈中可触及)的代码捕获,当然附近的代码也不可能处理它。由于异常每向上传递一次,就越靠近低层那些更加不知如何处理该异常的代码。

一旦异常来到Twisted的核心代码区,游戏也就结束了。异常并不会被处理,只是被记录下来。因此我们在以最原始的回调方式使用回调时(不使用deferred),必须在其进入Twisted之间很好地处理各种异常,至少是我们知道的那些在我们自己设定的规则下会产生的异常。当然其也应该包括那些由我们自己的BUG产生的异常。

由于bug可能存在于我们代码中的每个角落,因此我们必须将每个回调都放入try/except中,这样一来所有的异常都才有可能被捕获。这对于我们的errback同样适用,因为errback中也可能含有bugs


Deferred的优秀架构

最终还得由Deferred来帮我们解决这类问题。当一个deferred激活了一个callbackerrback时,它就会捕获各种由回调抛出的异常。换句话说,deferred扮演了try/except模块,这样一来,只要我们使用deferred就无需自己来实现这一层了。那deferred是如何解决这个问题的?很简单,它传递异常给在其链上的下一个errback

我们添加到deferred中的第一个errback回调来处理任何出错信息,信息是在deferrederrback函数调用时发出的。但第二个errback会处理任何由第一个errback或第一个callback抛出的异常,并一直按这种规则传递下去。

回忆下图12.我们假设第一对callback/errbackstage0,下面则是stage1stage2。。。依次类推。

对于stage N来说,如果其callbackerrback出错,那么stage N+1errback就会被调用并收到一个Failure对象作为参数,同时stage N+1callback就不会被调用了。

通过将回调函数产生的异常向在链中传递,deferred将异常抛向了高层代码。这也意味着调用deferredcallbackerrback永远不会在调用都本身处引发异常(只要你仅激活deferred一次),因此,底层的代码可以放心的激活deferred而无需担心会引发异常。相反,高层代码通过向deferred中添加errback(使用addErrback)来捕获异常。

在同步代码中,异常会在其被捕获而停止传递,那么一个errback如何发出其捕获了异常这一信号呢?同样很简单:不再引发异常。这样一来,执行权就转移到了callback中来。因此对于stage N来说,不管是callback还是errback成功执行而没有抛出异常,那么stage N+1callback就会被调用,同样,stage N+1errback就不会被调用了。

我们来总结一下吧:

1.一个deferred有一个callback/errback对链,它们以添加到deferred中的顺序依次排列

2.stage 0,即第一对errback/callbac,会在deferred激活时调用,具体调用那个看激活deferred的方式,若是通过.errback激活,则调用errback;同样若是通过.callback激活则调用callback。(这里的errback/callback实际是指通过addBoth添加的函数)

3.如果stage N执行出现异常,则stage N+1errback被调用,并且其参数即为stage N出现的异常

4.同样,如果stage N成功,即没有抛出异常,则N+1callback被调用,其第一个参数为stage N的返回值。

17更加直观的描述上述操作:

第九部分:第二个小插曲,Deferred

17deferred中的控制流程

绿色的线表示callbackerrback成功执行没抛出异常,而红线表示出现了异常。这些线不仅说明了控制流程还说明了异常与返回值在链中流动的情况。图17显示了所有deferred能出现的可能路径,但实际只有一条路径会存在。图18显示了一条可能的路径:


第九部分:第二个小插曲,Deferred

18:可能的deferred激活路线

18中,deferred.callback函数被调用了,因此激活了stage 0callback。这个callback成功的执行而没有抛出异常,因此控制权传给了stage 1callback。但这个callbac执行失败而抛出异常,因此控制权传给了stage 2errbackerrback成功的处理了异常,而没有再抛出异常,因此控制权传给了stage 3callback,并且将errback的返回值作为第一个参数传了进来(即stage 3callback中)。

18中,可以看出,最后一个stage上的所有的回调出现异常时,都由下一层的errback来捕获并处理,但如果最后一个stagecallbackerrback执行失败而抛出异常,怎么办呢?那么这个异常就会成为unhandled(未处理)。

在同步代码中,未处理的异常会导致解释器崩溃,在原始方式使用回调的代码中未处理异常会由reactor捕获并记录下来。那么未处理异常出现在deferred中会怎样呢?让我们来做个试验。运行twisted-deferred/defer-unhandled.py试试。下面是输出:

Finished
Unhandled error in Deferred: Traceback (most recent call last):
...
--- <exception caught here> ---
...
exceptions.Exception: oops

如下几点需要引起我们的注意:

1.最后一个print函数成功执行,意味着程序并没有因为出现未处理异常而崩溃。

2.其只是将跟踪栈打印出来,而没有宕掉解释器

3.跟踪栈的内容告诉我们deferred在何处捕获了异常

4.“’Unhandle”的字符在“Finished”之后出现。

之所以出现第4条是因为,这个消息只有在deferred被垃圾回收时才会打印出来。我们将在下面的部分看到其中的原因。

在同步代码中,我们可以使用raise来重新抛出一个异常而无需其它参数。同样,我们也可以在errback中这样做。deferred通过以下两点来判断callback/errback是否执行成功:

1.callback/errback “raise”一个异常,或

2.callbakc/errback返回一个Failure对象

因为errback的第一个参数就是一个Failure,因此一个errback可以在进行完其处理后可以再次抛出这个Failure


CallbacksErrbacks,成对出现

上面讨论内容中的一个问题必须要清楚:你添加callbackerrback到一个defered的顺序会决定这个deferred的的整体运行情况。另一个必须搞清楚的是:在一个deferredcallbackerrback往往是成对出现。有四个方法可以向一个deferred的回调链中添加callback/errback对:

  1. 1、addCallbacks

  2. 2、addCallback

  3. 3、addErrback

  4. 4、addBoth

很明显的是,第一个与第四个是向链中添加函数对。当然中间两个也向链中添加函数对。AddCallback向链中添加一个显式的callback函数与一个隐式的”pass-through“函数(实在想不出一个对应的词)。一个pass-through函数只是虚设的函数,只将其第一个参数返回。由于errback回调函数的第一个参数是Failure,因此一个“path-through”errback总是执行“失败”,即将异常传给下个errback回调。


deferred模拟器

这部分内容,没有译。其主要是帮助理解deferred,但你会发现,读其中的代码,根本更好的理解deferred。主要是我还没有理解,嘿嘿。所以就不知为不知吧。


总结

经过这些对回调的考虑,发现由于回调式编程改变了低层代码与高层代码的关系,因此让回调产生的异常直接抛到栈中并不件好事。Deferred通过将异常捕获然后将其顺着回调链传递来解决了这个问题。

我们同样意识到,原始数据(返回值)在链中被传递。结合这个两事实也就带来了这样一种场景:根据每个stage收到的结果的不同,deferredcallbackerrback链中来回交错传递数据并执行。

我们将在第十部分使用些学到的知识来更新我们的客户端。


本文转载自:http://blog.sina.com.cn/s/blog_704b6af70100py9n.html

共有 人打赏支持
水果糖
粉丝 15
博文 125
码字总数 51701
作品 0
深圳
程序员
(转) Twisted :第七部分 小插曲,Deferred

回调函数的后序发展 在第六部分我们认识这样一个情况: 回调是Twisted异步编程中的基础。除了与reactor交互外,回调可以安插在任何我们写的Twisted结构内。因此在使用Twisted或其它基于react...

水果糖 ⋅ 2016/01/27 ⋅ 0

(转) Twisted : 第十四部分 Deferred用于同步环境

介绍 这部分我们要介绍Deferred的另外一个功能。便于讨论,我们设定如下情景:假设由于众多的内部网请求一个外部诗歌下载服务器,但由于这个外部下载服务器性能太差或请求负荷太重。因此,我...

水果糖 ⋅ 2016/01/27 ⋅ 0

(转) Twisted : 第八部分 使用Deferred的诗歌下载客户端

客户端4.0 我们已经对deferreds有些理解了,现在我们可以使用它重写我们的客户端。你可以在twisted-client-4/get-poetry.py中看到它的实现。 这里的getpoetry已经再也不需要callback与errba...

水果糖 ⋅ 2016/01/27 ⋅ 0

(转) Twisted :第十九部分 改变之前的想法

简介 Twisted是一个正在进展的项目,它的开发者会定期添加新的特性并且扩展旧的特性. 随着Twisted 10.1.0发布,开发者向 类添加了一个新的特性—— ——这正是我们今天要研究的. 异步编程将请求...

水果糖 ⋅ 2016/01/27 ⋅ 0

(转) Twisted :第十八部分 Deferreds 全貌

简介 在上一个部分,我们学习了使用生成器构造顺序异步回调的新方法.这样,包括 ,我们现在有两种将异步操作链接在一起的方法. 有时,然而,我们需要"并行"的运行一组异步操作.由于Twisted是单线程...

水果糖 ⋅ 2016/01/27 ⋅ 0

(转) Twisted :第十部分 增强defer功能的客户端

现在我们将要向诗歌下载客户端添加一些新的处理逻辑,包括在第九部分提到要添加的功能。不过,首先我要说明一点:我并不知道如何实现Byronification引擎。那超出了我的编程能力范围。取而代之...

水果糖 ⋅ 2016/01/27 ⋅ 0

(转) Twisted : 第十二部分 改进诗歌下载服务器

新的服务器实现 这里我们要新写一个Twisted版的服务器。然后,再来讨论一些Deferred的新功能。 在第九、十部分,我们提出了诗歌转换引擎这个概念。由于其实现太过简单,因此我们用随机选择来...

水果糖 ⋅ 2016/01/27 ⋅ 0

(转) Twisted : 第十三部分 使用Deferred新功能实现新客户端

介绍 回忆下第10部分中的客户端5.1版。客户端使用一个Deferred来管理所有的回调链,其中包括一个格式转换引擎的调用。在那个版本中,这个引擎的实现是同步的。(即等待其执行再切到其它函数或...

水果糖 ⋅ 2016/01/27 ⋅ 0

[Python] Twiested - 基于事件驱动的网络编程

介绍 twisted 是python下一个事件驱动的网络引擎库, 支持很多种的协议. 它包含了一个web服务, 多种IM客户端,服务端, 邮件服务协议. 由于规模庞大, twisted分成了几个sub-project. 一起或者分...

长平狐 ⋅ 2013/06/03 ⋅ 0

[Python] Twiested - 基于事件驱动的网络编程

介绍 twisted 是python下一个事件驱动的网络引擎库, 支持很多种的协议. 它包含了一个web服务, 多种IM客户端,服务端, 邮件服务协议. 由于规模庞大, twisted分成了几个sub-project. 一起或者分...

长平狐 ⋅ 2013/06/03 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

开启Swarm集群以及可视化管理

在搭建的两台coreos服务器上开启swarm集群 前置条件: docker均开启2375端口 同一个局域网内 主服务器上安装Portainer容器 安装Portainer容器执行: docker run -d -p 9000:9000 --restart=a...

ykbj ⋅ 18分钟前 ⋅ 0

单例设计模式

1、单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例 2、饿汉式单例类 在这个类被加载时,静态变量instance会被初始化,此时类的私有构造子会被调用 饿汉式是典型...

职业搬砖20年 ⋅ 23分钟前 ⋅ 0

前端基础(四):前端国际规范收集

字数:1142 阅读时间:5分钟 前言 由于前端技术的灵活性和杂乱性,导致网上的许多解决方案不够全面甚至是完全错误,容易起到误导作用。所以,我对搜索到的解决方案往往是存疑态度。那么,如何...

老司机带你撸代码 ⋅ 25分钟前 ⋅ 0

Failed to open/create Network-VirtualBox Host-Only

虚拟机版本 : Oracle Vm VirtualBox 5.2.12 报错时机:开网卡二,重启虚拟机报错 "Failed to open/create the internal network 'HostInterfaceNetworking-VirtualBox Host-Only Ethernet Ada......

p至尊宝 ⋅ 28分钟前 ⋅ 0

三分钟学会如何在函数计算中使用 puppeteer

摘要: 使用 puppeteer 结合函数计算,可以快速的构建弹性的服务完成各种功能,包括:生成网页截图或者 PDF、高级爬虫,可以爬取大量异步渲染内容的网页、模拟键盘输入、表单自动提交、登录网...

阿里云云栖社区 ⋅ 32分钟前 ⋅ 0

springMVC接收表单时 Bean对象有Double Int Char类型的处理

前台ajax提交表单price为double类型 后台controller就介绍不到 400错误 前台 实体类: public class ReleaseMapIconConfig{ private String id; private long maxValue; private long minVal......

废柴 ⋅ 34分钟前 ⋅ 0

ZOOKEEPER安装

工作需要在ubuntu上配置了一个zookeeper集群,有些问题记录下来。 1. zookeeper以来java,所以首先要安装java。但是ubuntu系统有自带的jdk,需要通过命令切换java版本: $ sudo update-alter...

恰东 ⋅ 37分钟前 ⋅ 0

linux 进程地址空间的一步步探究

我们知道,在32位机器上linux操作系统中的进程的地址空间大小是4G,其中0-3G是用户空间,3G-4G是内核空间。其实,这个4G的地址空间是不存在的,也就是我们所说的虚拟内存空间。 那虚拟内存空间...

HelloRookie ⋅ 37分钟前 ⋅ 0

myatis #{}与${}区别及原理

https://blog.csdn.net/wo541075754/article/details/54292751

李道福 ⋅ 40分钟前 ⋅ 0

三分钟学会如何在函数计算中使用 puppeteer

摘要: 使用 puppeteer 结合函数计算,可以快速的构建弹性的服务完成各种功能,包括:生成网页截图或者 PDF、高级爬虫,可以爬取大量异步渲染内容的网页、模拟键盘输入、表单自动提交、登录网...

猫耳m ⋅ 41分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部