twisted系列教程十三–deferred 中的deferred
Introduction
回想一下第十部分的poetry client 5.1,client 用一個deferred 來管理一個callback 鏈,這個callback 鏈中調用了一個transformation 引擎,在client 5.1 中,這個引擎是作為一個同步的函數來實現的.
現在我們想寫一個新的client,讓它利用我們在第十二部分寫的transformation service.問題就來了:既然transformation service 是通過網絡訪問的,我們需要用異步的I/O.這就意味著我們用來請求transformation 的api 是異步的.也就是說我們的try_to_cummingsify callback 會返回一個Deferred 對象
在一個deferred 鏈中的callback 返回另一個deferred 的時候會發生什么?讓我們把第一個deferred 叫做外部的deferred 第二個deferred 叫做內部的deferred.假設在外部的deferred 中callback N 返回了 內部的deferred.這個callback 是在說”我是異步的,我的結果還沒有來”.因為外部的deferred 需要調用下一個callback 或者errback,并傳遞當前callback 的返回值,外部的deferred 需要等待內部的deferred被觸發.當然,外部的deferred 也不能是阻塞的,所以,此時外部的deferred暫定callback 鏈的執行并把控制圈交還給reactor.
外部的deferred 是怎樣知道什么時候恢復呢? 很簡單,通過給內部的deferred增加一個callback/errback 對.當內部的deferred 被觸發的時候,外部的deferred 會恢復執行.假如內部的deferred成功了(例如:它調用了一個被外部的deferred增加的callback),外部的deferred則會繼續調用它的N+1callback,假如內部的deferred 失敗了,外部的deferred 會調用它的 N+1 errback.
讓我們用一張圖片來描述這個過程,圖片二十八:
圖片二十吧
在這張圖片中,外部的deferred 有四對callback/errback.當外部的deferred 被觸發時,第一個callback 返回了一個deferred(內部的deferred).這時候外部的deferred會停止觸發它的callback 鏈,并把控制權交給reactor(在給內部的deferred 的增加了一對callback/errback 之后).然后,一段時間之后,內部的deferred觸發,外部的deferred 也開始恢復運行.注意,外部的deferred 并不會自己觸發內部的deferred.那也是不可能的,因為外部的deferred 不會知道什么時候內部的deferred的結果是可用的,或者結果是什么.我們的外部的deferred就是異步的等待內部的deferred 觸發.
注意連接callback 和內部的deferred 的那跟線是黑色的而不是綠色或紅色.那是因為我們我不知這個callback 是成功還是失敗知道內部的deferred觸發.直到那時候外部的deferred 才能知道是去調用下一個callback 還是下一個errback.
圖片二十九描述了相同的外部的/內部的deferred 觸發順序,不過是站在reactor 的角度:
圖片二十九
這個可能是deferred 最難懂的部分,如果你在短時間內不能消化也不要著急.我們會用具體的程序舉例說明–twisted-deferred/defer-10.py.這個例子創造了兩個外部的deferred,一個帶有空白的callbacks,另一個有一個callback 并返回一個內部的deferred.通過學習這個例子你可以搞明白第二個外部的deferred是怎樣在內部的deferred 返回的時候停止的,和 在內部的deferred被觸發的時候外部的deferred 又是怎樣恢復的.
Client 6.0
讓我們用我們新學的嵌套的deferred 來重新實現一下我們的poetry client,并用上第十二部分講到的transformation service,你可以在twisted-client-6/get-poetry.py 找到代碼. poetry protocol 和protocol factory 和前一版本的client 都沒有變化.但是增加了進行transformation 請求的protocol 和factory.下面是protocol 部分:
class TransformClientProtocol(NetstringReceiver):
????def connectionMade(self):
????????self.sendRequest(self.factory.xform_name,
?????????????????????????????????????????self.factory.poem)
????def sendRequest(self, xform_name, poem):
????????self.sendString(xform_name + '.' + poem)
????def stringReceived(self, s):
????????self.transport.loseConnection()
????????self.poemReceived(s)
????def poemReceived(self, poem):
????????self.factory.handlePoem(poem)
使用NetstringReceiver 作為一個基類讓這個protocol 相當的簡單.只要連接一建立,我們從factory 中取到變形的名字和詩的內容并向server發送一個transform 請求.當我們得到返回的詩,我們把它傳遞給factory,下面是factory 的代碼:
class TransformClientFactory(ClientFactory):
????protocol = TransformClientProtocol
????def __init__(self, xform_name, poem):
????????self.xform_name = xform_name
????????self.poem = poem
????????self.deferred = defer.Deferred()
????def handlePoem(self, poem):
????????d, self.deferred = self.deferred, None
????????d.callback(poem)
????def clientConnectionLost(self, _, reason):
????????if self.deferred is not None:
????????????d, self.deferred = self.deferred, None
????????????d.errback(reason)
????clientConnectionFailed = clientConnectionLost
這個factory 是被client 設計的,處理一個transformation 請求,并存儲著transform 的名字和這首詩的內容.factory 創造了一個代表了這個transformation請求返回結果的deferred.注意這個factory 是怎樣處理兩種錯誤情況的:一個是連接錯誤的情況一個是還沒完全接受到返回值的時候連接就斷開的情況.也注意clientConnectionLost 方法就算我們接受詩成功最后也會調用,但是在這種情況下self.deferred 已經被handlepoem 設置為None了.
這個factory 創建了一個deferred 也觸發了它,這是一個很好的方法:
一般來說,一個創造了deferred 的對象,也應該負責觸發那個deferred
這個”你創造它,你觸發它”規則幫助我們保證一個deferred 僅僅被觸發一次,也讓程序流程更簡單一些.
除了這個transform Factory,這里還有一個proxy 類,它隱藏連接transform server 的具體信息:
class TransformProxy(object):
????"""
????I proxy requests to a transformation service.
????"""
????def __init__(self, host, port):
????????self.host = host
????????self.port = port
????def xform(self, xform_name, poem):
????????factory = TransformClientFactory(xform_name, poem)
????????from twisted.internet import reactor
????????reactor.connectTCP(self.host, self.port, factory)
????????return factory.deferred
這個類提供了一個xform()接口,其他的代碼可以用它來發送transformations 請求.所以其他的代碼就可以僅僅發送一個transformations 請求然后得到一個deferred,而不用再去關心ip 和端口號.
其他的代碼變化的地方還有 try_to_cummingsify callback:
def try_to_cummingsify(poem):
????d = proxy.xform('cummingsify', poem)
????def fail(err):
????????print >>sys.stderr, 'Cummingsify failed!'
????????return poem
????return d.addErrback(fail)
這個callback 現在返回一個deferred,但是我們不用改變其他的main 函數中的代碼.因為try_to_cummingsify 本來就在deferred 的鏈中,它已經是異步的了,其他的就不用變化了.
你可能會發現我們返回的是d.addErrback(fail) 的結果,這里是用了一些語法糖.addCallback 和 addErrback 都返回原來的deferred.我們也可以寫成:
d.addErrback(fail)
return d
Testing out the Client
這個新版的client 和其他的client相比有一些語法上的變化,假如你有一個transformation service 運行在10001 端口,兩個poetry server 運行在10002 和 10003 上,你應該這樣啟動client:
python twisted-client-6/get-poetry.py 10001 10002 10003
你可以這樣啟動transformation service :
python twisted-server-1/transformedpoetry.py --port 10001
這樣啟動poetry server:
python twisted-server-1/fastpoetry.py --port 10002 poetry/fascination.txt
python twisted-server-1/fastpoetry.py --port 10003 poetry/science.txt
Wrapping Up
在這一部分我們學習了在一個callback 鏈中一個deferred 怎樣透明的處理其他的deferred.我們可以安全的增加異步的callback到一個外部的deferred 中.這個是非常有用的因為我們的很多函數都要求是異步的.
我們知道deferred 的全部的事情了么? 還沒有.還有很重要的一點,我們會在第十四部分講到.
---
20120821 16:43 這一段比較重要:“
外部的deferred 是怎樣知道什么時候恢復呢? 很簡單,通過給內部的deferred增加一個callback/errback 對.當內部的deferred 被觸發的時候,外部的deferred 會恢復執行.假如內部的deferred成功了(例如:它調用了一個被外部的deferred增加的callback),外部的deferred則會繼續調用它的N+1callback,假如內部的deferred 失敗了,外部的deferred 會調用它的 N+1 errback.”
總結
以上是生活随笔為你收集整理的twisted系列教程十三–deferred 中的deferred的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 下的init 0,1,2,3
- 下一篇: Python 中 assert的使用位置