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