(转) Twisted :第七部分 小插曲,Deferred

回调函数的后序发展html

在第六部分咱们认识这样一个状况:python

    回调是Twisted异步编程中的基础。除了与reactor交互外,回调能够安插在任何咱们写的Twisted结构内。所以在使用Twisted或其它基于reactor的异步编程体系时,都意味须要将咱们的代码组织成一系列由reactor循环能够激活的回调函数链。react

即便一个简单的get_poetry函数都须要回调,两个回调函数中一个用于处理正常结果而另外一个用于处理错误。做为一个Twisted程序员,咱们必须充分利用这一点。应该花点时间思考一下如何更好地使用回调及使用过程当中会遇到什么困难。git

分析下3.1版本中的get_poetry函数:程序员

...
def got_poem(poem):
    print poem
    reactor.stop()
def poem_failed(err):
    print >>sys.stderr, 'poem download failed'
    print >>sys.stderr, 'I am terribly sorry'
    print >>sys.stderr, 'try again later?'
    reactor.stop()
get_poetry(host, port, got_poem, poem_failed)
 
reactor.run()

咱们想法很简单:github

1.若是完成诗歌下载,那么就打印它编程

2.若是没有下载到诗歌,那就打印出错误信息异步

3.上面任何一种状况出现,都要中止程序继续运行异步编程

同步程序中处理上面的状况会采用以下方式:函数

...
try:
    poem = get_poetry(host, port) # the synchronous version of get_poetry
except Exception, err:
    print >>sys.stderr, 'poem download failed'
    print >>sys.stderr, 'I am terribly sorry'
    print >>sys.stderr, 'try again later?'
    sys.exit()
else:
    print poem
    sys.exit()


callback相似else处理路径,而errback相似except处理路径。这意味着激活errback回调函数相似于同步程序中抛出一个异常,而激活一个callback意味着同步程序中的正常执行路径。

两个版本有什么不一样以外吗?能够明确的是,在同步版本中,Python解释器能够确保只要get_poetry抛出何种类型的异步都会执行except块。即只要咱们相信Python解释器可以正确的解释执行Python程序,那么就能够相信异常处理块会在恰当的时间点被执行。

不异步版本相反的是:poem_failed错误回调是由咱们本身的代码激活并调用的,即PeotryClientFactoryclientConnectFailed函数。是咱们本身而不是Python来确保当出错时错误处理代码可以执行。所以咱们必须保证经过调用携带Failure对象的errback来处理任何可能的错误。

不然,咱们的程序就会由于等待一个永远不会出现的回调而止步不前。

这里显示出了同步与异步版本的又一个不一样之处。若是咱们在同步版本中没有使用try/except捕获异步,那么Python解释器会为咱们捕获而后关掉咱们的程序并打印出错误信息。可是若是咱们忘记抛出咱们的异步异常(在本程序中是在PoetryClientFactory调用errback),咱们的程序会一直运行下去,还开心地觉得什么事都没有呢。

显而易见,在异步程序中处理错误是至关重要的,甚至有些严峻。也能够说在异步程序中处理错误信息比处理正常的信息要重要的多,这是由于错误会以多种方式出现,而正确的结果出现的方式是惟一的。当使用Twisted编程时忘记处理异常是一个常犯的错误。

关于上面同步程序代码的另外一个默认实事是:elseexcept块二者只能是运行其中一个(假设咱们的get_poetry没有在一个无限循环中运行)。Python解释器不会忽然决定二者都运行或突发奇想来运行else27次。对于经过Python来实现那样的动做是不可能的。

但在异步程序中,咱们要负责callbackerrback的运行。所以,咱们可能就会犯这样的错误:同时调用了callbackerrback或激活callback27次。这对于使用get_poetry的用户来讲是不幸的。虽然在描述文档中没有明确地说明,像try/except块中的elseexcept同样,对于每次调用get_poetrycallbackerrback只能运行其中一个,不论是咱们是否成功得下载完诗歌。

设想一下,咱们在调试某个程序时,咱们提出了三次诗歌下载请求,可是获得有7callback被激活和2errback被激活。可能这时,你会下来检查一下,何时get_poetry激活了两次callback而且还抛出一个错误出来。

从另外一个视角来看,两个版本都有代码重复。异步的版本中含有两次reactor.stop,同步版本中含有两次sys.exit调用。咱们能够重构同步版本以下:

...
try:
    poem = get_poetry(host, port) # the synchronous version of get_poetry
except Exception, err:
    print >>sys.stderr, 'poem download failed'
    print >>sys.stderr, 'I am terribly sorry'
    print >>sys.stderr, 'try again later?'
else:
    print poem
 
sys.exit()


咱们能够以一样的方式来重构异步版本吗?说实话,确实不太可能,由于callbackerrback是两个不一样的函数。难道要咱们回到使用单一回调来实现重构吗?

好下面是咱们在讨论使用回调编程时的一些观点:

1.激活errback是很是重要的。因为errback的功能与except块相同,所以用户须要确保它们的存在。他们并不可选项,而是必选项。

2.不在错误的时间点激活回调与在正确的时间点激活回调同等重要。典型的用法是,callbackerrback是互斥的即只能运行其中一个。

3.使用回调函数的代码重构起来有些困难。

来下面的部分,咱们还会讨论回调,可是已经能够明白为何Twisted引入了deferred抽象机制来管理回调了。


Deferred

因为架设在异步程序中大量被使用,而且咱们也看到了,正确的使用这一机制须要一些技巧。所以,Twisted开发者设计了一种抽象机制-Deferred-以让程序员在使用回调时更简便。

一个Deferred有一对回调链,一个是为针对正确结果,另外一个针对错误结果。新建立的Deferred的这两条链是空的。咱们能够向两条链里分别添加callbackerrback。其后,就能够用正确的结果或异常来激活Deferred。激活Deferred意味着以咱们添加的顺序激活callbackerrback。图12展现了一个拥有callback/errback链的Deferred对象:

第七部分:小插曲,Deferred

12: Deferred


因为defered中不使用reactor,因此咱们能够不用在事件循环中使用它。也许你在Deferred中发现一个seTimeout的函数中使用了reactor。放心,它未来未来的版本中删掉。

下面是咱们第一人使用deferred的例子twisted-deferred/defer-1.py:

from twisted.internet.defer import Deferred
 
def got_poem(res):
    print 'Your poem is served:'
    print res
 
def poem_failed(err):
    print 'No poetry for you.'
 
d = Deferred()
 
# add a callback/errback pair to the chain
d.addCallbacks(got_poem, poem_failed)
 
# fire the chain with a normal result
d.callback('This poem is short.')
 
print "Finished"


代码开始建立了一个新deferred,而后使用addCallbacks添加了callback/errback对,而后使用callback函数激活了其正常结果处理回调链。固然了,因为只含有一个回调函数还算不上链,但没关系,运行它:

Your poem is served:
This poem is short.
Finished

有几个问题须要注意:

1.正如3.1版本中咱们使用的callback/errback对,添加到deferred中的回调函数只携带一个参数,正确的结果或出错信息。其实,deferred支持回调函数能够有多个参数,但至少得有一个参数而且第一个只能是正确的结果或错误信息。

2.咱们向deferred添加的是回调函数对

3.callbac函数携带仅有的一个参数即正确的结果来激活deferred

4.从打印结果顺序能够看出,激活的deferred当即调用了回调。没有任何异步的痕迹。这是由于没有reactor参与致使的。

好了,让咱们来试试另一种状况,twisted-deferred/defer-2.py激活了错误处理回调:

from twisted.internet.defer import Deferred
from twisted.python.failure import Failure
 
def got_poem(res):
    print 'Your poem is served:'
    print res
 
def poem_failed(err):
    print 'No poetry for you.'
 
d = Deferred()
 
# add a callback/errback pair to the chain
d.addCallbacks(got_poem, poem_failed)
 
# fire the chain with an error result
d.errback(Failure(Exception('I have failed.')))
 
print "Finished"

运行它打印出的结果为:

No poetry for you.
Finished

激活errback链就调用errback函数而不是callback,而且传进的参数也是错误信息。正如上面那样,errbackdeferred激活就被调用。

在前面的例子中,咱们将一个Failure对象传给了errbackdeferred会将一个Exception对象转换成Failure,所以咱们能够这样写:

from twisted.internet.defer import Deferred
 
def got_poem(res):
    print 'Your poem is served:'
    print res
 
def poem_failed(err):
    print err.__class__
    print err
    print 'No poetry for you.'
 
d = Deferred()
 
# add a callback/errback pair to the chain
d.addCallbacks(got_poem, poem_failed)
 
# fire the chain with an error result
d.errback(Exception('I have failed.'))


运行结果以下:

twisted.python.failure.Failure [Failure instance: Traceback (failure with no frames): : I have failed. ]
No poetry for you.

这意味着在使用deferred时,咱们能够正常地使用Exception。其中deferred会为咱们完成向Failure的转换。

下面咱们来运行下面的代码看看会出现什么结果:

from twisted.internet.defer import Deferred
def out(s): print s
d = Deferred()
d.addCallbacks(out, out)
d.callback('First result')
d.callback('Second result')
print 'Finished'


输出结果:

First result Traceback (most recent call last): ... twisted.internet.defer.AlreadyCalledError

很意外吧,也就是说deferred不容许别人激活它两次。这也就解决了上面出现的那个问题:一个激活会致使多个回调同时出现。而deferred设计机制控制住了这种可能,若是你非要在一个deferred上要激活多个回调,那么正如上面那样,会报异常错。

deferred能帮助咱们重构异步代码吗?考虑下面这个例子:

import sys
 
from twisted.internet.defer import Deferred
 
def got_poem(poem):
    print poem
    from twisted.internet import reactor
    reactor.stop()
 
def poem_failed(err):
    print >>sys.stderr, 'poem download failed'
    print >>sys.stderr, 'I am terribly sorry'
    print >>sys.stderr, 'try again later?'
    from twisted.internet import reactor
    reactor.stop()
 
d = Deferred()
 
d.addCallbacks(got_poem, poem_failed)
 
from twisted.internet import reactor
 
reactor.callWhenRunning(d.callback, 'Another short poem.')
 
reactor.run()

这基本上与咱们上面的代码相同,惟一不一样的是加进了reactor。咱们在启动reactor后调用了callWhenRunning函数来激活deferred。咱们利用了callWhenRunning函数能够接收一个额外的参数给回调函数。多数TwistedAPI都以这样的方式注册回调函数,包括向deferred添加callbackAPI。下面咱们给deferred回调链添加第二个回调:

import sys
 
from twisted.internet.defer import Deferred
 
def got_poem(poem):
    print poem
 
def poem_failed(err):
    print >>sys.stderr, 'poem download failed'
    print >>sys.stderr, 'I am terribly sorry'
    print >>sys.stderr, 'try again later?'
 
def poem_done(_):
    from twisted.internet import reactor
    reactor.stop()
 
d = Deferred()
 
d.addCallbacks(got_poem, poem_failed)
d.addBoth(poem_done)
 
from twisted.internet import reactor
 
reactor.callWhenRunning(d.callback, 'Another short poem.')
 
reactor.run()


addBoth函数向callbackerrback链中添加了相同的回调函数。在这种方式下,deferred有可能也会执行errback链中的回调。这将在下面的部分讨论,只要记住后面咱们还会深刻讨论deferred


总结:

在这部分咱们分析了回调编程与其中潜藏的问题。咱们也认识到了deferred是如何帮咱们解决这些问题的:

1.咱们不能忽视errback,在任何异步编程的API中都须要它。Deferred支持errbacks

2.激活回调屡次可能会致使很严重的问题。Deferred只能被激活一次,这就相似于同步编程中的try/except的处理方法。

3.含有回调的程序在重构时至关困难。有了deferred,咱们就经过修改回调链来重构程序。

关于deferred的故事尚未结束,后面还有大量的细节来说。但对于使用它来重构咱们的客户端已经够用的了,在第八部分将讲述这部份内容。

相关文章
相关标签/搜索