twisted系列教程七–小插曲,延迟对象
在第六部分,我們得出這樣一個(gè)結(jié)論:callbacks 是twisted異步編程的一個(gè)重要組成部分.callback 是交織在twisted結(jié)構(gòu)中的,而不僅僅是連接reactor 的一種方法.所以用twisted 或者其他的基于reactor 的異步程序,就意味著把我們的程序組織成被reator觸發(fā)的callback鏈.
甚至像我們的簡(jiǎn)單的get_poetry方法都需要兩個(gè)callback,一個(gè)用來(lái)處理正常的返回結(jié)果,另一個(gè)用來(lái)處理錯(cuò)誤.作為一個(gè)twisted 的程序員,我們將來(lái)會(huì)大量的用到它們.我們應(yīng)該花費(fèi)點(diǎn)時(shí)間想想使用callback 的最好的方法,還有我們將會(huì)遇到那些陷阱.
看一下下面的一段代碼,在client 3.1 版本中:
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)單:
????如果得到了詩(shī),打印出來(lái)
????如果沒(méi)有得到詩(shī),打印一個(gè)錯(cuò)誤
????在上面兩種情況之后,結(jié)束程序
如果是同步的類(lèi)似的程序應(yīng)該像下面這樣:
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 代碼塊.也就意味著,異步程序的觸發(fā)errback就像拋出一個(gè)異常而觸發(fā)callback就像走正常的程序流.
這兩個(gè)版本代碼之間的不同之處是什么?在同步的版本中,python 的解釋器會(huì)保證只要get_poetry 拋出任何異常,except塊就會(huì)運(yùn)行.如果我們能相信python解釋器能正確運(yùn)行代碼,我就能相信出錯(cuò)處理部分的代碼能運(yùn)行.
和異步程序相比:poem_failed errback 會(huì)被我們的代碼觸發(fā)–PoetryClientFactory中的clientConnectionFailed,是我們而不是python 在控制出錯(cuò)假如出錯(cuò)的話,所以我們要確保要去處理每一個(gè)會(huì)出錯(cuò)的情況,并觸發(fā)相應(yīng)的errback.否則的話我們的程序?qū)?huì)等待一個(gè)永遠(yuǎn)不會(huì)來(lái)的callback然后卡在那里.
這個(gè)是同步程序和異步程序的令一個(gè)區(qū)別.在同步程序中,如果我們不去捕捉出現(xiàn)的異常,python 解釋器會(huì)幫我們捕捉它然后程序崩潰掉并告訴我們出現(xiàn)了什么錯(cuò)誤.但是假如我們忘記拋出一個(gè)異步的錯(cuò)誤,我們的程序就會(huì)不停停止,過(guò)起了什么也不直到的性福生活.
很明顯的,在異步程序中處理異常是很重要的,也是很棘手的.在異步程序中出錯(cuò)處理比處理正常的結(jié)果更重要,因?yàn)槭虑槌鲥e(cuò)的方法遠(yuǎn)比正常運(yùn)行的方法多.在用twisted過(guò)程中,忘記處理錯(cuò)誤是一經(jīng)常犯的錯(cuò)誤.
對(duì)上面的額同步程序來(lái)說(shuō):else 代碼塊和except 代碼塊都會(huì)只運(yùn)行一次,python 解釋器并不會(huì)忽然的決定要運(yùn)行它們兩個(gè),或者一時(shí)高興,運(yùn)行了else 代碼塊27次.
但是在異步程序中,callback 和errback 都是由我們來(lái)控制的,我們可能會(huì)犯錯(cuò).我們可能既調(diào)用了callback 也調(diào)用了errback,或者調(diào)用了callback 27次.然后調(diào)用我們寫(xiě)的get_poetry 的那個(gè)娃可就要悲劇了. 雖然twisted 中沒(méi)有明確說(shuō),但就像同步程序中的try/except 一樣,在twisted 中,我們要調(diào)用callback 一次 或者調(diào)用errback 一次.運(yùn)行g(shù)et_poetry一次,我們或者得到詩(shī)歌或者沒(méi)有得到.
試著想一下如果你去調(diào)試一個(gè)發(fā)起了三個(gè)poetry請(qǐng)求并調(diào)用了七個(gè)callback 和兩個(gè)errback 的程序, 你可以從哪里下手呢? 你可能會(huì)結(jié)束你的callbacks 和errbacks 然后去監(jiān)測(cè)在一個(gè)get_poetry請(qǐng)求中他們什么時(shí)候被觸發(fā)了兩次.
還有一點(diǎn)就是:兩個(gè)版本的get_poetry都有一些重復(fù)的代碼.異步的版本調(diào)用了兩次reactor.stop同步的版本調(diào)用了兩次sys.exit.我們可以把同步版本的重構(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()
異步的版本可以重構(gòu)嗎?我們對(duì)此還不太確定,因?yàn)槲覀儺惒降膅et_poetry callback 和errback是兩個(gè)不同的函數(shù),難道你想讓我們把它簡(jiǎn)化成一個(gè)callback?
ok,下面是我們對(duì)使用callback進(jìn)行編程的一些看法:
????調(diào)用errback 非常重要.因?yàn)閑rrback就是except語(yǔ)句,用戶(hù)需要依賴(lài)它們,它們不是可選的
????不要在錯(cuò)誤的時(shí)間觸發(fā)callback和在正確的時(shí)間觸發(fā)它們一樣重要,callback 和errback 是互赤,只能運(yùn)行一個(gè)
????在使用callback的時(shí)候,代碼是不容易重構(gòu)的
我們?cè)趯?lái)的章節(jié)仍會(huì)講callbacks,但現(xiàn)在我們要去看有沒(méi)有一個(gè)抽象可以很好的管理callbacks.
The Deferred
callback在異步程序中使用的非常多,但就我們發(fā)現(xiàn)如果要正確的使用它們是很困難的.twisted 的開(kāi)發(fā)者們創(chuàng)造了一個(gè)叫做Deferred 的抽象可以幫助我們來(lái)處理callbacks.Deferred 類(lèi)在twisted.internet.defer中定義.
一個(gè)deferred 一對(duì)callback 鏈,一個(gè)是用來(lái)處理正確結(jié)果的,另一個(gè)是用來(lái)處理出錯(cuò)結(jié)果的.一個(gè)新的deferred 含有兩個(gè)空的鏈.我們可以通過(guò)增加callbacks 和 errbacks 來(lái)填充這兩條鏈,然后用一個(gè)正常的結(jié)果或者異常來(lái)觸發(fā)deferred.觸發(fā)deferred 會(huì)按照callback或errback被加入進(jìn)去的順序調(diào)用它們.圖片十二描述了帶有callback/errback 鏈的deferred 實(shí)例.
圖片十二
讓我們測(cè)試一下,因?yàn)閐eferred 沒(méi)有用到reactor,我們不用開(kāi)啟一個(gè)循環(huán),我們的第一個(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"
這段代碼新建了一個(gè)新的deferred,然后用addCallbacks加入了一對(duì)callback/errback.然后callback觸發(fā)了正常結(jié)果的callback.當(dāng)然這里并沒(méi)有一個(gè)callback 鏈,只有一個(gè)callback.但不管怎么樣,運(yùn)行這個(gè)代碼然后輸出如下:
Your poem is served:
This poem is short.
Finished
非常簡(jiǎn)單,下面是需要注意的一些東西:
????就像在client 3.1我們用的callback/errback,我們向deferred 中加入的callback一次接收一個(gè)參數(shù),或者一個(gè)正常的結(jié)果或者一個(gè)錯(cuò)誤的結(jié)果.實(shí)際上deferred支持callback和errback帶有多個(gè)參數(shù),但是最少一個(gè).但第一個(gè)參數(shù)永遠(yuǎn)是callback 或者errback
????我們?cè)黾觕allbacks和errbacks的時(shí)候是一對(duì)對(duì)的
????callback方法用一個(gè)正常的結(jié)果觸發(fā)deferred
????看一下輸出的順序,我們可以看到觸發(fā)defferred之后立即調(diào)用了callback.這里根本沒(méi)有異步,因?yàn)闆](méi)有用reactor.
讓我們看另一個(gè)例子在twisted-deferred/defer-2.py,這次觸發(fā)deferred 的errback:
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)行之后有如下輸出:
No poetry for you.
Finished
所以要觸發(fā)errback 鏈只要調(diào)用errback方法 就可以了,這個(gè)方法參數(shù)是一個(gè)錯(cuò)誤的返回結(jié)果.就像之前的callback一樣,errback在觸發(fā)之后立即被調(diào)用了.
在上一個(gè)例子中,我們傳遞一個(gè)Failure對(duì)象給errback,這樣沒(méi)什么問(wèn)題.但是deferred 會(huì)自動(dòng)把普通的Exception 轉(zhuǎn)變?yōu)镕ailures對(duì)象,我們?cè)趖wisted-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.'))
在這里我們傳遞給errback 一個(gè)正常的Exception.我們得到如下的輸出:
twisted.python.failure.Failure
[Failure instance: Traceback (failure with no frames): : I have failed.
]
No poetry for you.
這就意味著當(dāng)我們用deferred 的時(shí)候,我們可以用正常的Exception,deferred 會(huì)自動(dòng)的為我們轉(zhuǎn)為Failure對(duì)象.defferred 會(huì)保證每一個(gè)errback被觸發(fā)的時(shí)候都會(huì)被傳入一個(gè)Failure 實(shí)例.
我嘗試著按了callback 的按鈕也嘗試著按了errback 的按鈕.就像任何一個(gè)好的工程師一樣,你可能想要一遍一遍的按它們.為了讓代碼更短一些,我們讓callback和errback 都是同一個(gè)函數(shù).記住它們返回的內(nèi)容不同,一個(gè)是正常的結(jié)果,一個(gè)是錯(cuò)誤.代碼在這里twisted-deferred/defer-4.py:
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'
你會(huì)得到如下的輸出:
First result
Traceback (most recent call last):
...
twisted.internet.defer.AlreadyCalledError
灰常有意思,defferred 不會(huì)讓我們多次觸發(fā)正常的callback.實(shí)際上,不管如何defferred 都不讓人觸發(fā)兩次,下面的例子會(huì)證明這一點(diǎn):
????twisted-deferred/defer-4.py
????twisted-deferred/defer-5.py
????twisted-deferred/defer-6.py
????twisted-deferred/defer-7.py
注意最后的print 語(yǔ)句都沒(méi)有運(yùn)行到. 當(dāng)我們用defferred來(lái)管理我們的callbacks 的時(shí)候,我們就不會(huì)范既調(diào)用callback 又調(diào)用errback 的錯(cuò)誤.我們可以試一下,但是deferred會(huì)拋出異常來(lái)提醒我們.
deferred 可以幫助我們重構(gòu)異步的代碼嗎?讓我們看一下例子twisted-deferred/defer-8.py:
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()
這個(gè)是我們?cè)嫉拇a.注意我們?cè)趩?dòng)reactor之后,用callWhenRunning來(lái)觸發(fā)defferred.我們利用callWhenRunning 接收額外的參數(shù)然后傳遞給callback.在twisted 中有很多注冊(cè)callback 的api都遵守這個(gè)規(guī)則,包括吧callback 加進(jìn)deferred 的api.
callback 和 errback 都可以停止reactor,既然defferred 支持callback 鏈和errback鏈,我們可以這些普通代碼重組進(jìn)鏈的第二層,具體的代碼在這里twisted-deferred/defer-9.py:
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 方法向callback和errback 鏈中加入了相同的函數(shù),不論如果我們完成了重構(gòu).
Summary
在這一部分我們分析了callback 程序,知道了一些可能潛在的問(wèn)題.我們也看到了Defferred是怎樣幫助我們寫(xiě)代碼的:
????我們不能無(wú)視errbacks.deferred內(nèi)置對(duì)errback 的支持
????多次觸發(fā)callback可能導(dǎo)致很難調(diào)試的bug,Deferred只能被觸發(fā)一次,你可以把他想象成try/except
????用deferred,我們可以通過(guò)向鏈中增加新的callback和errback,并在各callback 和errback中移動(dòng)代碼完成重構(gòu)
我們跟deferred 還沒(méi)完,它的很多原理我們還沒(méi)有講,但對(duì)于我們的poetry client已經(jīng)夠用了.繼續(xù)等我們的第八部分吧.我要先去吃飯了.
總結(jié)
以上是生活随笔為你收集整理的twisted系列教程七–小插曲,延迟对象的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C/C++ 线程三种并发方式比较(传统互
- 下一篇: TCP协议端口状态说明:CLOSE-WA