在第六部分我們認(rèn)識(shí)到這樣一個(gè)情況:回調(diào)是Twisted異步編程中的基礎(chǔ)。除了與reactor交互外,回調(diào)可以安插在任何我們寫的Twisted結(jié)構(gòu)內(nèi)。因此在使用Twisted或其它基于reactor的異步編程體系時(shí),都意味需要將我們的代碼組織成一系列由reactor循環(huán)可以激活的回調(diào)函數(shù)鏈。
即使一個(gè)簡(jiǎn)單的get_poetry函數(shù)都需要回調(diào),兩個(gè)回調(diào)函數(shù)中一個(gè)用于處理正常結(jié)果而另一個(gè)用于處理錯(cuò)誤。作為一個(gè)Twisted程序員,我們必須充分利用這一點(diǎn)。應(yīng)該花點(diǎn)時(shí)間思考一下如何更好地使用回調(diào)及使用過(guò)程中會(huì)遇到什么困難。
分析下3.1版本中的get_poetry函數(shù):
...
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()
我們想法很簡(jiǎn)單:
同步程序中處理上面的情況會(huì)采用如下方式:
...
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回調(diào)函數(shù)類似于同步程序中拋出一個(gè)異常,而激活一個(gè)callback意味著同步程序中的正常執(zhí)行路徑。 兩個(gè)版本有什么不同之外嗎?可以明確的是,在同步版本中,Python解釋器可以確保只要get_poetry拋出異常(任意類型, 這里特指Exception及其子類)就會(huì)執(zhí)行except塊。也就是說(shuō)只要我們相信Python解釋器能夠正確的解釋執(zhí)行Python程序,那么就可以相信異常處理塊會(huì)在恰當(dāng)?shù)臅r(shí)間點(diǎn)被執(zhí)行。
和異步版本不同的是:poem_failed錯(cuò)誤回調(diào)是由我們自己的代碼激活并調(diào)用的,即PoetryClientFactory的clientConnectFailed函數(shù)。是我們自己而不是Python來(lái)確保當(dāng)出錯(cuò)時(shí)錯(cuò)誤處理代碼能夠執(zhí)行。因此我們必須保證通過(guò)調(diào)用攜帶Failure對(duì)象的errback來(lái)處理任何可能的錯(cuò)誤。
否則,我們的程序就會(huì)因?yàn)榈却粋€(gè)永遠(yuǎn)不會(huì)出現(xiàn)的回調(diào)而止步不前。
這里顯示出了同步與異步版本的又一個(gè)不同之處。如果我們?cè)谕桨姹局袥](méi)有使用try/except捕獲異步,那么Python解釋器會(huì)為我們捕獲然后關(guān)掉我們的程序并打印出錯(cuò)誤信息。但是如果我們忘記拋出我們的異步異常(在本程序中是在PoetryClientFactory調(diào)用errback),我們的程序會(huì)一直運(yùn)行下去,還開心地以為什么事都沒(méi)有呢。
顯而易見,在異步程序中處理錯(cuò)誤是相當(dāng)重要的,甚至有些嚴(yán)峻。也可以說(shuō)在異步程序中處理錯(cuò)誤信息比處理正常的信息要重要的多,這是因?yàn)殄e(cuò)誤會(huì)以多種方式出現(xiàn),而正確的結(jié)果出現(xiàn)的方式是唯一的。當(dāng)使用Twisted編程時(shí)忘記處理異常是一個(gè)常犯的錯(cuò)誤。
關(guān)于上面同步程序代碼的另一個(gè)默認(rèn)的事實(shí)是:else與except塊兩者只能是運(yùn)行其中一個(gè)(假設(shè)我們的get_poetry沒(méi)有在一個(gè)無(wú)限循環(huán)中運(yùn)行)。Python解釋器不會(huì)突然決定兩者都運(yùn)行或突發(fā)奇想來(lái)運(yùn)行else塊27次。對(duì)于通過(guò)Python來(lái)實(shí)現(xiàn)那樣的動(dòng)作是不可能的。
但在異步程序中,我們要負(fù)責(zé)callback和errback的運(yùn)行。因此,我們可能就會(huì)犯這樣的錯(cuò)誤:同時(shí)調(diào)用了callback與errback或激活callback27次。這對(duì)于使用get_poetry的用戶來(lái)說(shuō)是不幸的。雖然在描述文檔中沒(méi)有明確地說(shuō)明,像try/except塊中的else與except一樣,對(duì)于每次調(diào)用get_poetry時(shí)callback與errback只能運(yùn)行其中一個(gè),不管我們是否成功的下載完詩(shī)歌。
設(shè)想一下,我們?cè)谡{(diào)試某個(gè)程序時(shí),我們提出了三次詩(shī)歌下載請(qǐng)求,但是得到有7次callback被激活和2次errback被激活??赡苓@時(shí),你會(huì)下來(lái)檢查一下,什么時(shí)候get_poetry激活了兩次callback并且還拋出一個(gè)錯(cuò)誤出來(lái)。
從另一個(gè)視角來(lái)看,兩個(gè)版本都有代碼重復(fù)。異步的版本中含有兩次reactor.stop,同步版本中含有兩次sys.exit調(diào)用。我們可以重構(gòu)同步版本如下:
...
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()
我們可以以同樣的方式來(lái)重構(gòu)異步版本嗎?說(shuō)實(shí)話,確實(shí)不太可能,因?yàn)閏allback與errback是兩個(gè)不同的函數(shù)。難道要我們回到使用單一回調(diào)來(lái)實(shí)現(xiàn)重構(gòu)嗎?
好, 下面是我們?cè)谟懻撌褂没卣{(diào)編程時(shí)的一些觀點(diǎn):
后面的部分我們還會(huì)討論回調(diào),但是到這里已經(jīng)可以明白為什么Twisted引入了抽象機(jī)制(Deferred)來(lái)管理回調(diào)了。
由于回調(diào)在異步程序中大量被使用,并且正確的使用這一機(jī)制需要一些技巧。因此,Twisted開發(fā)者設(shè)計(jì)了一種抽象機(jī)制-Deferred-讓程序員在使用回調(diào)時(shí)更加簡(jiǎn)便。
一個(gè)Deferred有一對(duì)回調(diào)鏈,一個(gè)是為針對(duì)正確結(jié)果,另一個(gè)針對(duì)錯(cuò)誤結(jié)果。新創(chuàng)建的Deferred的這兩條鏈?zhǔn)强盏?。我們可以向兩條鏈里分別添加callback與errback。其后,就可以用正確的結(jié)果或異常來(lái)激活Deferred。激活Deferred意味著以我們添加的順序激活callback或errback。圖12展示了一個(gè)擁有callback/errback鏈的Deferred對(duì)象:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p07_deferred-1.png" alt="" />
由于defered中不使用reactor,所以使用它并不需要啟動(dòng)事件循環(huán)。也許你在Deferred中發(fā)現(xiàn)一個(gè)setTimeout的函數(shù)中使用了reactor。放心,它已經(jīng)廢棄并且會(huì)在將來(lái)的版本中刪掉,我們可以直接無(wú)視它。
下面是我們看看第一個(gè)使用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"
代碼開始創(chuàng)建了一個(gè)新deferred,然后使用addCallbacks添加了callback/errback對(duì),然后使用callback函數(shù)激活了其正常結(jié)果處理回調(diào)鏈。當(dāng)然了,由于只含有一個(gè)回調(diào)函數(shù)還算不上鏈,但不要緊,運(yùn)行它:
Your poem is served:
This poem is short.
Finished
有幾個(gè)問(wèn)題需要注意:
好了,讓我們來(lái)試試另外一種情況,twisted-deferred/defer-2.py激活了錯(cuò)誤處理回調(diào):
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"
運(yùn)行它打印出的結(jié)果為:
No poetry for you.
Finished
激活errback鏈就調(diào)用errback函數(shù)而不是callback,并且傳進(jìn)的參數(shù)也是錯(cuò)誤信息。正如上面那樣,errback在deferred激活就被調(diào)用。
在前面的例子中,我們將一個(gè)Failure對(duì)象傳給了errback。deferred會(huì)將一個(gè)Exception對(duì)象轉(zhuǎn)換成Failure,因此我們可以這樣寫twisted-deferred/defer-3.py:
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.'))
運(yùn)行結(jié)果如下:
twisted.python.failure.Failure
[Failure instance: Traceback (failure with no frames): : I have failed.
]
No poetry for you.
這意味著在使用deferred時(shí),我們可以正常地使用Exception。其中deferred會(huì)為我們完成向Failure的轉(zhuǎn)換。
下面我們來(lái)運(yùn)行代碼twisted-deferred/defer4.py看看會(huì)出現(xiàn)什么結(jié)果:
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'
輸出結(jié)果:
First result
Traceback (most recent call last):
...
twisted.internet.defer.AlreadyCalledError
很意外吧,也就是說(shuō)deferred不允許別人激活它兩次。這也就解決了上面出現(xiàn)的那個(gè)問(wèn)題:一個(gè)激活會(huì)導(dǎo)致多個(gè)回調(diào)同時(shí)出現(xiàn)。而deferred設(shè)計(jì)機(jī)制控制住了這種可能,如果你非要在一個(gè)deferred上要激活多個(gè)回調(diào),那么正如上面那樣,會(huì)報(bào)異常錯(cuò)。
這里展示了更多的例子:
那deferred能幫助我們重構(gòu)異步代碼嗎?考慮下面twisted-deferred/defer-8.py這個(gè)例子:
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()
這基本上與我們上面的代碼相同,唯一不同的是加進(jìn)了reactor。我們?cè)趩?dòng)reactor后調(diào)用了callWhenRunning函數(shù)來(lái)激活deferred。我們利用了callWhenRunning函數(shù)可以接收一個(gè)額外的參數(shù)給回調(diào)函數(shù)。多數(shù)Twisted的API都以這樣的方式注冊(cè)回調(diào)函數(shù),包括向deferred添加callback的API。下面我們給deferred回調(diào)鏈添加第二個(gè)回調(diào):
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函數(shù)向callback與errback鏈中添加了相同的回調(diào)函數(shù)。在這種方式下,deferred有可能也會(huì)執(zhí)行errback鏈中的回調(diào)。這將在下面的部分討論,只要記住后面我們還會(huì)深入討論deferred。
在這部分我們分析了回調(diào)編程與其中潛藏的問(wèn)題。我們也認(rèn)識(shí)到了deferred是如何幫我們解決這些問(wèn)題的:
關(guān)于deferred的故事還沒(méi)有結(jié)束,后面還有大量的細(xì)節(jié)來(lái)講。但對(duì)于使用它來(lái)重構(gòu)我們的客戶端已經(jīng)夠用的了,在第八部分將講述這部分內(nèi)容。
本部分原作參見: dave @ http://krondo.com/?p=1682
本部分翻譯內(nèi)容參見楊曉偉的博客 http://blog.sina.com.cn/s/blog_704b6af70100q52t.html