twisted系列教程九–Deferred 的第二个小插曲
?More Consequence of Callbacks
我們將要再來研究一下callback,盡管我們已經對deferred比較了解而且已經可以寫出twisted 風格的異步程序,Deferred 類提供了更多的特色來進行處理一些更復雜的設置.所以我們要想出一些更復雜的設置來看看用callback編程的時候能給我們造成哪些挑戰.然后我們會研究deferred是怎樣處理這些挑戰的.
為了激發我們繼續討論,我們將會向我們的poetry client增加一個新的功能.假設某的蛋痛的科學家發明出了一個特別的算法,這個算法可以將一首詩變成另一首詩.而且我們的導師提供了一個參照的實現,用下面的接口來實現:
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.
????"""
就像很多新的軟件的軟件一樣,這個實現有很多bug.這意味著除了注釋中說的錯誤以外,byronificate方法如果遇到一些特殊情況也會拋出隨機的異常.我們也會假設我們的引擎運行的足夠快,下面是我們對我們的程序的期望:
????試著下載一首小詩
????如果下載失敗,告訴用戶我們不能完成下載
????如果我們下載完成了,試著用IByronificationEngine進行轉化
????如果這個引擎拋出了GibberishError錯誤,告訴用戶我們不能完成下載
????如果這個引擎拋出了另外的異常,則保持原來的詩不變
????如果我們得到了一首詩,打印出來
????結束程序
?
這個構想是說如果出現了GibberishError 則意味著我們不能得到最終的詩,我們會告訴用戶下載失敗.這對調試沒什么作用,但是我們的用戶最想知道的是詩有沒有下載成功.另一方面,假如這個引擎由于某些原因出現了失敗,我們直接傳給用戶我們從服務器上接收到的詩.畢竟有些東西總比什么也沒有強.
這里是我們程序的同步版本:
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()
這個代碼通過重構還可以更簡單一些,但是它已經把要做的描述的很清晰了.我們想把我們的twisted poetry client4.0也改成這個結構,我們會在第十部分完成.現在,我們先來改造我們的client 3.1,client 3.1 沒有用deferred.假設我們不用考慮來處理異常,僅僅把got_poem callback 改成這樣:
def got_poem(poem):
????poems.append(byron_engine.byronificate(poem))
????poem_done()
在byronificate 方法發生拋出GibberishError 或者其他異常的時候會發生什么? 看看第六部分的圖片十一,我們可以看到:
????在factory中,異常會被傳遞到poem_finished callback,那個方法最終觸發callback
????因為poem_finished 不會捕捉異常,它會繼續運行至protocol中的poemReceived
????然后運行到connectionLost
????然后就是運行到twisted 內部了,最后停止reactor
?
根據我們已經學過的,reactor 會捕捉和記錄異常而不是崩潰掉,但它肯定不會做的就是告訴用戶它不能下載一首詩.reactor 不會了解任何的詩或者GibberishErrors,它只是一般性的一塊代碼,被用來處理各種各樣的網絡連接.
現在注意,在上面講的每一步,異常被傳遞到越來越具有一般性的代碼上.在got_poem 以后的每一步都不適合用來處理異常,這種情況是和同步程序中的異常傳播的方法是完全相同的.看一下圖片十五,一張同步程序中的調用的堆棧信息:
圖片十五
主方法是”high-context”,意味著他對整個程序都了解,為什么它存在,它是讓整個程序運轉的.典型的例子是,main方法可以利用命令行的參數來表明用戶想讓這個程序做什么.
連接socket 的方法是”low-context”的.它所知道的就是去連接一個網絡地址.它不知道令一邊是什么也不知道為什么我們需要連接.connet 是一個很具有一般性的方法–你可以不管你連接的是什么服務.
get_poetry 則在中間,它知道他要獲取一首詩,但是不知道如果沒有獲取到該怎么辦.
所以一個被connect拋出的異常會往上拋,從一般性的low-context到具有特殊功能的high-context,直到異常遇到可以處理它的代碼.
異常是逐漸往上拋的而不是迭帶的尋找”high-context”代碼.在一個典型的同步程序中”up the stack ” 和”towards higher-context” 是相同的方向(這里實在不直到怎么翻譯好).
現在回想一下我們將要對client 3.1 進行的改造,堆棧信息將會如下-圖片十六:
圖片十六
問題現在很明顯了:在一個callback過程中,reactor(low-context) 會調用”high-context” 的代碼,并以此類推.假如一個異常沒有被立即的處理,這個異常會向”low-context”代碼傳遞,”low-context” 對異常一無所知,所以這個異常就不會被處理.
一但一個異常被傳進到twisted 的內部代碼,這個程序就要崩潰掉了.這個異常不會被處理,它頂多會被reactor記錄一下.所以當我們不用deferred編程的時候,我們必須認真的處理每一個異常.
因為bug無處不在,我們應該需要在我們的callback外層都包裝一個try/except,以便異常可以被很好的處理,errback 也同樣需要啊,因為處理錯誤的代碼也可能出錯哇.
The Fine Structure of Deferreds
Deferred已經幫我們解決這個問題了,當deferred觸發callback 或者errback 的時候,它捕捉任何的異常.換句話說,一個deferred相當于包裝在外層的try/except,所以根本不用我們自己寫這個包裝.但是deferred怎么處理捕捉到的exception?很簡單—它把異常(以Failue 的方式)傳遞給下一個errback.
所以第一個被deferred加入的errback會處理所以向deferred發出錯誤信號的異常,第二個errback來處理第一個第一個errback或者callback拋出的異常,然后一直下去.
回想一下圖片十二,描述了一個帶有callback和errback鏈的deferred.我們假設第一個的callback/errback對為第0層,下一對為第1層,并依次類推.
在第n層,假如callback 或者errback 失敗了,則第n+1層的errback會被觸發,并傳遞一個Failure 對象.
通過傳遞exception,deferred把異常往”higher-context”方向傳,這就意味著觸發deferred 的callback 或者errback方法對觸發者來說不會造成什么異常.所以底層的代碼可以安全的觸發deferred而不用關心捕捉異常.相反的,高層的代碼可以通過向deferred中增加errback來捕捉異常.
在同步的程序中,異常只要被捕捉住就會停止傳播.所以errback怎樣標明自己已經捕捉駐異常了呢? 很簡單,通過不再拋出那個異常.在這種情況下,執行轉向callback,在第n層,如果callback 或者errback執行成功了,都會轉向第n+1層的callback.
讓我們來總結一下deferred 的觸發模式:
????一個deferred 包含一個有序的callback/errback 鏈,按照它們被加入的順序排列
????在第0層,第一個callback/errback對會被觸發當deferred被callback方法觸發的時候,第0層的callback會被調用,如果deferred被errback觸發則第0層的errback被調用
????如果第n層失敗,則第n+1層的errback被調用
????如果第n層成功,則第n+1層的callback調用
?
可以用下面的圖片十七來描述:
圖片十七
圖中綠色的線表示了當一個callback或者errback成功的時候運行的過程,而紅色的線則表示了失敗的時候運行的路線.圖片十七展示了所有的情況,但一個callback/errback對 只會有一個可以運行,圖片十八展示了其中的一個過程:
圖片十八
在圖片十八中,deferred 的callback方法被調用,并觸發了第0層的callback.這個callback成功了然后轉向第1層的callback,…….(略去了,整個調用過程一看就明白)
在圖片十八中,我們已經指出第3層會成功的執行,但要是在第3層的callback中失敗了呢,后面沒有errback可以繼續處理異常,我們就說錯誤是沒有被處理的.
在同步程序中沒有被處理的異常會讓程序崩潰掉,在一個不用deferred 的異步程序中,一個沒有被處理的異常會被reactor捕捉和記錄.在deferred中如果有一個沒有被處理的異常會發生什么呢?讓我們來看一下.例子在twisted-deferred/defer-unhandled.py,例子中會觸發一個只有一個callback 的deferred,并會拋出一個異常,下面是輸出:
Finished
Unhandled error in Deferred:
Traceback (most recent call last):
...
--- ---
...
exceptions.Exception: oops
一些要注意的事情:
????最后的print語句運行了,所以這個程序沒有因為異常而崩潰掉
????那意味著traceback已經輸出了,python 的解釋器沒有崩潰掉
????traceback 的內容告訴了我們deferred對象在哪里捕捉了異常
????"unhandled" 的提示在打印出"Finished"以后被輸出
?
所以當你用deferred 的時候,在callback中沒有處理的異常仍然會被記下,作為調試用.但是它們不會讓程序崩潰掉(實際上異常根本不會到達reactor,deferred會首先捕捉到它們).順便說一下,”Finished” 比”Unhandled”早輸出的原因是”Unhandled”語句直到deferred 被垃圾回收的時候才被輸出.我們在將來的章節會講到.
我們可以在同步程序中用raise 再一次拋出異常,這樣做可以重新拋出原來的異常并可以讓采取一些措施來處理錯誤但又不完全處理完.我們在errback中也可以這么做,如果出現下列的一些情況,deferred會認為callback/errback 已經失敗:
????callback/errback 拋出任何的異常
????callback/errback 返回Failure 對象
?
既然errback 的第一個參數是Failure,errback 可以通過返回這個參數重新拋出異常,然后讓后面的程序去捕捉它.
Callbacks and Errbacks, Two by Two 你需要明白的是,你向deferred 中加入的callback/errback 的順序對于deferred怎樣被觸發有很大的影響.在一個deferred中,callback 和errback 往往是成對出現的.有四種方法你可以向Deferred中加入callback/errback:
????addCallbacks
????addCallback
????addErrback
????addBoth
?
很明顯的,第一個和最后一個向callback/errback鏈中加入一對,addCallback 方法增加一個明確的callback 和一個隱含的errback,這個隱含的errback僅僅返回它的第一個參數,第一個參數永遠是一個Failure 對象.addErrback 同理.
The Deferred Simulator
熟悉deferred 觸發它們的callback和errback 是很重要的.twisted-deferred/deferred-simulator.py是一個deferred 模擬器.運行這個程序會讓你進入一系列的callback 和errback.你并可以指定它們的返回結果.
Summary 在探討這么多的callback 之后,我們意識到讓exception往上冒泡并不是一個很好的選擇,因為callback部分的程序處在low-context 和high-context 之間.Deferred 可以處理這個情況,通過捕捉異常然后把它們送往下層的callback/errback,而不是讓異常傳遞到reactor中去. 我們還學到了正常的返回結果也會向下運行,這樣的話錯誤結果和正常結果就組成了一種交叉調用的模式.
有了這些,我們會在第十部分更新我們的poetry client.
總結
以上是生活随笔為你收集整理的twisted系列教程九–Deferred 的第二个小插曲的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 绘图解谜:公钥、私钥、证书
- 下一篇: UID和GID