更多關於回調的知識html
稍微停下來再思考一下回調的機制。儘管對於以Twisted方式使用Deferred寫一個簡單的異步程序已經很是瞭解了,但Deferred提供更多的是隻有在比較複雜環境下才會用到的功能。所以,下面咱們本身想出一些複雜的環境,以此來觀察當使用回調編程時會遇到哪些問題。而後,再來看看deferred是如何解決這些問題的。python
所以,咱們爲詩歌下載客戶端添加了一個假想的功能。設想一些計算機科學家發明了一種新詩歌關聯算法,react
Byronification引擎。這個漂亮的算法根據一首詩歌生成一首使用Lord Byron式的一樣的詩歌。另外,專家們提供了其Python的接口,即:git
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. """
像大多數高尖端的軟件同樣,其實現都存在着許多bugs。這意外着除了已知的異常外,這個byronificate 方法可能會拋出一些專家當時沒有預料到的異常出來。github
咱們還能夠假設這個引擎可以很是快的動做以致於咱們能夠在主線程中調用到而無需考慮使用reactor。下面是咱們想讓程序實現的效果:算法
1.嘗試下載詩歌編程
2.若是下載失敗,告訴用戶沒有獲得詩歌網絡
3.若是下載到詩歌,則轉交給Byronificate處理引擎一份架構
4.若是引擎拋出GibberishError,告訴用戶沒有獲得詩歌app
5.若是引擎拋出其它異常,則將原始式樣的詩歌立給用戶
6.若是咱們獲得這首詩歌,則打印它
7.結束程序
這裏設計是當遇到GibberishError異常則表示沒有獲得詩歌,所以咱們直接告訴用戶下載失敗便可。這也許對調試沒什麼用處,但咱們的用戶關心的只是咱們下載到詩歌沒有。另外一方面,若是引擎由於一些其它的緣由而出現處理失敗,那麼咱們將原始詩歌交給用戶。畢竟,有詩歌呈現總比沒有好,雖然不是用戶想要的Byron樣式。
下面是同步模式的代碼:
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()
這段代碼可能通過一些重構會更加簡單,但已經足以說明上面的邏輯流程。咱們想升級那些最近使用deferred的客戶端來使用這個功能。但這部份內容我準備把它放在第十部分。如今,咱們來考慮一下,用版本3.1來實現這個功能,最後一個沒有使用deferred的客戶端。假設咱們無需考慮處理異常,那麼只是改變一下got_poem回調便可:
def got_poem(poem): poems.append(byron_engine.byronificate(poem)) poem_done()
那麼若是byronificate拋出GibberishError異常或其它異常會發生什麼呢?看看第六部分的圖11,咱們能夠獲得:
1.這個異常會傳播到工廠中的poem_finished回調,即激活got_poem的方法
2.因爲poem_finished並無捕獲這個異常,所以其會傳遞到protocol中的poemReceive函數
3.而後來到connectionLost函數,仍然在protocol中
4.而後就來到Twisted的核心區,最後止步於reactor。
前面已經瞭解到,reactor會捕獲異常並記錄它而不是「崩潰」掉。但它卻不會告訴用戶咱們的詩歌下載失敗的消息。reactor並不知道任何詩歌或GibberishError
s的信息,它只是一段被設計成適應全部網絡類型的通用代碼,即使與詩歌無關的網絡服務。(Dave這裏想強調的是reactor只是作一些具備廣泛意義的事情,不會單獨去處理特定的問題,例如這裏原GibberishError
s異常)
注意異常是如何順着調用鏈傳遞到具備通用性代碼區域。而且看到,在got_poem後面任何一步都沒有可望以咱們客戶端的具體要求來處理異常的。這與同步代碼中的方式偏偏相反。
圖15揭示了一個同步客戶端的調用棧:
圖15:同步調用棧
main函數是最高層,意味着它能夠觸及整個程序,它爲何要存在,而且它是如何在總體上表現的。典型的,main函數能夠觸及到用戶在命令行輸入想讓程序作什麼的參數。而且它還有一個特殊的目的:爲一個命令行式的客戶端打印結果。
socket的connet函數,偏偏相反,其爲最低層。它所知道的就是提供到指定地址的鏈接。它並不知道另外一端是什麼及咱們爲何要進行鏈接。但connect有通用性,無論你由於何種服務要進行網絡鏈接均可以使用它。
get_poetry在中間,它知道要取一些詩歌,但並不知道若是得不到詩歌會發生什麼。所以,從connect拋出的異常會向上傳遞,從低層的具備通用性的代碼區到高層的具備針對性的代碼區,直到其傳遞到知道如何處理這個異常的代碼區。
如今,咱們再回來看看對3.1版的假想功能的實現。咱們在圖16裏對調用棧進行了分析,固然只是說明了其中關鍵的函數:
圖16 異步調用棧
如今問題很是清晰了:在回調中,低層的代碼(reactor)調用高層的代碼,其甚至還會調用更高層的代碼。所以一旦出現了異常,它並不會當即被其附件(在調用棧中可觸及)的代碼捕獲,固然附近的代碼也不可能處理它。因爲異常每向上傳遞一次,就越靠近低層那些更加不知如何處理該異常的代碼。
一旦異常來到Twisted的核心代碼區,遊戲也就結束了。異常並不會被處理,只是被記錄下來。所以咱們在以最原始的回調方式使用回調時(不使用deferred),必須在其進入Twisted之間很好地處理各類異常,至少是咱們知道的那些在咱們本身設定的規則下會產生的異常。固然其也應該包括那些由咱們本身的BUG產生的異常。
因爲bug可能存在於咱們代碼中的每一個角落,所以咱們必須將每一個回調都放入try/except中,這樣一來全部的異常都纔有可能被捕獲。這對於咱們的errback一樣適用,由於errback中也可能含有bugs。
Deferred的優秀架構
最終還得由Deferred來幫咱們解決這類問題。當一個deferred激活了一個callback或errback時,它就會捕獲各類由回調拋出的異常。換句話說,deferred扮演了try/except模塊,這樣一來,只要咱們使用deferred就無需本身來實現這一層了。那deferred是如何解決這個問題的?很簡單,它傳遞異常給在其鏈上的下一個errback。
咱們添加到deferred中的第一個errback回調來處理任何出錯信息,信息是在deferred的errback函數調用時發出的。但第二個errback會處理任何由第一個errback或第一個callback拋出的異常,並一直按這種規則傳遞下去。
回憶下圖12.咱們假設第一對callback/errback是stage0,下面則是stage1,stage2。。。依次類推。
對於stage N來講,若是其callback或errback出錯,那麼stage N+1的errback就會被調用並收到一個Failure對象做爲參數,同時stage N+1的callback就不會被調用了。
經過將回調函數產生的異常向在鏈中傳遞,deferred將異常拋向了高層代碼。這也意味着調用deferred的callback與errback永遠不會在調用都自己處引起異常(只要你僅激活deferred一次),所以,底層的代碼能夠放心的激活deferred而無需擔憂會引起異常。相反,高層代碼經過向deferred中添加errback(使用addErrback)來捕獲異常。
在同步代碼中,異常會在其被捕獲而中止傳遞,那麼一個errback如何發出其捕獲了異常這一信號呢?一樣很簡單:再也不引起異常。這樣一來,執行權就轉移到了callback中來。所以對於stage N來講,不論是callback仍是errback成功執行而沒有拋出異常,那麼stage N+1的callback就會被調用,一樣,stage N+1的errback就不會被調用了。
咱們來總結一下吧:
1.一個deferred有一個callback/errback對鏈,它們以添加到deferred中的順序依次排列
2.stage 0,即第一對errback/callbac,會在deferred激活時調用,具體調用那個看激活deferred的方式,如果經過.errback激活,則調用errback;一樣如果經過.callback激活則調用callback。(這裏的errback/callback實際是指經過addBoth添加的函數)
3.若是stage N執行出現異常,則stage N+1的errback被調用,而且其參數即爲stage N出現的異常
4.一樣,若是stage N成功,即沒有拋出異常,則N+1的callback被調用,其第一個參數爲stage N的返回值。
圖17更加直觀的描述上述操做:
圖17:deferred中的控制流程
綠色的線表示callback和errback成功執行沒拋出異常,而紅線表示出現了異常。這些線不只說明了控制流程還說明了異常與返回值在鏈中流動的狀況。圖17顯示了全部deferred能出現的可能路徑,但實際只有一條路徑會存在。圖18顯示了一條可能的路徑:
圖18:可能的deferred激活路線
圖18中,deferred的.callback函數被調用了,所以激活了stage 0的callback。這個callback成功的執行而沒有拋出異常,所以控制權傳給了stage 1的callback。但這個callbac執行失敗而拋出異常,所以控制權傳給了stage 2的errback。errback成功的處理了異常,而沒有再拋出異常,所以控制權傳給了stage 3的callback,而且將errback的返回值做爲第一個參數傳了進來(即stage 3的callback中)。
圖18中,能夠看出,最後一個stage上的全部的回調出現異常時,都由下一層的errback來捕獲並處理,但若是最後一個stage的callback或errback執行失敗而拋出異常,怎麼辦呢?那麼這個異常就會成爲unhandled(未處理)。
在同步代碼中,未處理的異常會致使解釋器崩潰,在原始方式使用回調的代碼中未處理異常會由reactor捕獲並記錄下來。那麼未處理異常出如今deferred中會怎樣呢?讓咱們來作個試驗。運行twisted-deferred/defer-unhandled.py試試。下面是輸出:
Finished Unhandled error in Deferred: Traceback (most recent call last): ... --- <exception caught here> --- ... exceptions.Exception: oops
以下幾點須要引發咱們的注意:
1.最後一個print函數成功執行,意味着程序並無由於出現未處理異常而崩潰。
2.其只是將跟蹤棧打印出來,而沒有宕掉解釋器
3.跟蹤棧的內容告訴咱們deferred在何處捕獲了異常
4.「’Unhandle」的字符在「Finished」以後出現。
之因此出現第4條是由於,這個消息只有在deferred被垃圾回收時纔會打印出來。咱們將在下面的部分看到其中的緣由。
在同步代碼中,咱們可使用raise來從新拋出一個異常而無需其它參數。一樣,咱們也能夠在errback中這樣作。deferred經過如下兩點來判斷callback/errback是否執行成功:
1.callback/errback 「raise」一個異常,或
2.callbakc/errback返回一個Failure對象
由於errback的第一個參數就是一個Failure,所以一個errback能夠在進行完其處理後能夠再次拋出這個Failure。
Callbacks與Errbacks,成對出現
上面討論內容中的一個問題必需要清楚:你添加callback與errback到一個defered的順序會決定這個deferred的的總體運行狀況。另外一個必須搞清楚的是:在一個deferred中callback與errback每每是成對出現。有四個方法能夠向一個deferred的回調鏈中添加callback/errback對:
一、addCallbacks
二、addCallback
三、addErrback
四、addBoth
很明顯的是,第一個與第四個是向鏈中添加函數對。固然中間
兩個也向鏈中添加函數對。
AddCallback
向鏈中添加一個顯式的
callback
函數與一個隱式的」
pass-through「
函數(實在想不出一個對應的詞)。一個
pass-through
函數只是虛設的函數,只將其第一個參數返回。因爲
errback
回調函數的第一個參數是
Failure
,所以一個「
path-through」
的
errback
老是執行「失敗」,即將異常傳給下個
errback
回調。
deferred
模擬器
這部份內容,沒有譯。其主要是幫助理解deferred,但你會發現,讀其中的代碼,根本更好的理解deferred。主要是我尚未理解,嘿嘿。因此就不知爲不知吧。
總結
通過這些對回調的考慮,發現因爲回調式編程改變了低層代碼與高層代碼的關係,所以讓回調產生的異常直接拋到棧中並不件好事。Deferred經過將異常捕獲而後將其順着回調鏈傳遞來解決了這個問題。
咱們一樣意識到,原始數據(返回值)在鏈中被傳遞。結合這個兩事實也就帶來了這樣一種場景:根據每一個stage收到的結果的不一樣,deferred在callback與errback鏈中來回交錯傳遞數據並執行。
咱們將在第十部分使用些學到的知識來更新咱們的客戶端。