客戶端4.0python
咱們已經對deferreds有些理解了,如今咱們可使用它重寫咱們的客戶端。你能夠在twisted-client-4/get-poetry.py中看到它的實現。react
這裏的get_poetry已經不再須要callback與errback參數了。相反,返回了一個用戶可能根據須要添加callbacks和errbacks的新deferred。git
def get_poetry(host, port): """ Download a poem from the given host and port. This function returns a Deferred which will be fired with the complete text of the poem or a Failure if the poem could not be downloaded. """ d = defer.Deferred() from twisted.internet import reactor factory = PoetryClientFactory(d) reactor.connectTCP(host, port, factory) return d
這裏的工廠使用一個deferred而不callback/errback對來初始化。一旦咱們獲取到poem後或者沒有鏈接到服務器上,deferred就會以返回一首詩歌或一個failure的被激活。github
class PoetryClientFactory(ClientFactory): protocol = PoetryProtocol def __init__(self, deferred): self.deferred = deferred def poem_finished(self, poem): if self.deferred is not None: d, self.deferred = self.deferred, None d.callback(poem) def clientConnectionFailed(self, connector, reason): if self.deferred is not None: d, self.deferred = self.deferred, None d.errback(reason)
注意咱們在deferred被激活後是如何銷燬其引用的。這種方式普便存在於Twisted的源代碼中,這樣作能夠保證咱們不會激活一個deferred兩次。這也爲Python的垃圾回收帶來的方便。編程
這裏仍然不用去改變poetryProtocol。咱們只須要更新poetry_main函數便可:服務器
def poetry_main(): addresses = parse_args() from twisted.internet import reactor poems = [] errors = [] def got_poem(poem): poems.append(poem) def poem_failed(err): print >>sys.stderr, 'Poem failed:', err errors.append(err) def poem_done(_): if len(poems) + len(errors) == len(addresses): reactor.stop() for address in addresses: host, port = address d = get_poetry(host, port) d.addCallbacks(got_poem, poem_failed) d.addBoth(poem_done) reactor.run() for poem in poems: print poem
注意咱們是如何利用deferred的回調鏈在不考慮兩個主要的callback與errback回調外,重構poem_done調用的。架構
因爲deferred在Twisted大量被使用,使用小寫字母d來表示當前正在工做中的deferred已經成爲慣例。app
討論異步
新版本的客戶端與咱們前面的同步版本的客戶端同樣,get_poetry獲得的參數都是詩歌下載服務器的地址。同步版本返回的是詩歌內容,而異步版本返回的倒是一個deferred。返回一個deferred是Twisted的APIs或用Twisted寫的程序常見的,這樣一來咱們能夠這樣來理解deferred:異步編程
一個Deferred表明了一個「異步的結果」或者「結果尚未到來」
在圖13中能夠更加清晰地表達出二者之間的不一樣:
圖13:同步 VS 異步
異步函數返回一個deferred,對用戶意味着:
我是一個異步函數。無論你想要什麼,可能如今立刻都得不到。但當結果來到時,我會激活這個deferred的callback鏈並返回結果。或者當出錯時,相應地激活errback鏈並返回出錯信息。
固然,這個函數是不能隨意激活這個deferred的,由於它已經返回了。但這個函數已經啓動了一系列事件,這些事件最終將會激活這個deferred。
所以,deferred是爲適應異步模式的一種延遲函數返回的方式。函數返回一個deferred意味着其是異步的,表明着未來的結果,也是對未來可以返回結果的一種承諾。
同步函數也能返回一個deferred,所以嚴格點說,返回deferred只說多是異步的。咱們會在未來的例子中看到同步函數返回deferred。
因爲deferred的行爲已經很好的定義與理解,所以在實現本身的API時返回一個deferred更容易讓其它的Twisted程序理解你的代碼。若是沒有deferred,可能每一個人寫的模塊都使用不一樣的方式來處理回調。這要一來就增長了相互理解的工做量。
當你使用Deferred時,你仍然在使用回調,它們仍然由reactor來調用。
當首次學習Twisted時,常常犯的一個錯誤就是:會給deferred增長一些它自己不能實現的功能。尤爲是:常常假設在deferred上添加一個函數就可使其變成異步函數。這可能會讓你產生這樣的想法:在Twisted 中能夠經過將os.system的函數添加到deferred的回調鏈中。
我認爲,這多是沒有弄清楚異步編程的緣由才產生這樣的想法。因爲Twisted代碼使用了大量的deferred但卻不多會涉及到reactor,可能會認爲deferred作了大部分工做。若是你是從開始閱讀這個系列的,你就會知道事情遠不是這樣。雖然Twisted是由衆多部分組合在一塊兒來工做的,但實現異步的主要工做都是由reactor來完成的。Deferred是一個很好的抽象概念,但前面幾個例子中的客戶端咱們卻沒有使用它,而reactor卻都用到了。
來看看咱們第一個回調激活時的跟蹤棧信息。運行twisted-client-4/get-poetry-stack.py讓其鏈接你打開的服務器:
File "twisted-client-4/get-poetry-stack.py", line 129, in poetry_main() File "twisted-client-4/get-poetry-stack.py", line 122, in poetry_main reactor.run() ... # some more Twisted function calls protocol.connectionLost(reason) File "twisted-client-4/get-poetry-stack.py", line 59, in connectionLost self.poemReceived(self.poem) File "twisted-client-4/get-poetry-stack.py", line 62, in poemReceived self.factory.poem_finished(poem) File "twisted-client-4/get-poetry-stack.py", line 75, in poem_finished d.callback(poem) # here's where we fire the deferred ... # some more methods on Deferreds File "twisted-client-4/get-poetry-stack.py", line 105, in got_poem traceback.print_stack()
這很像版本2.0的跟蹤棧,圖14能夠很好地說明具體的調用關係:
圖14 deferred的回調
這很相似於咱們前面的Twisted客戶端,雖然這張圖的調用關係並不清晰而會你摸不着頭腦。但咱們先不深刻分析這張圖。有一個細節並無在這張圖上反映出來:callback鏈直到第二個回調poem_done激活前纔將控制權還給reactor。
經過使用deferred,咱們在由Twisted中的reactor啓動的回調中加入了一些本身的東西,但咱們並無改變異步程序的基礎架構。回憶下回調編程的特色:
1.在一個時刻,只會有一個回調在運行
2.當reactor運行時,那咱們本身的代碼則得不到運行
3,反之則反之
4.若是咱們的回調函數發生阻塞,那麼整個程序就跟着阻塞掉了
在一個 deferred上追加一個回調並不會改變上面這些實事。尤爲是,第4 條。所以當一個deferred激活時被阻塞,那麼整個Twisted就會陷入阻塞中。所以咱們會獲得以下結論:
Deferred只是解決回調函數管理問題的一種解決方案。它並不一種替代回調方式也不能將阻塞式的回調變成非阻塞式回調的。
我經過構建一個添加阻塞式回調的deferred來驗證最後一點。驗證代碼文件爲twisted-deferred/defer-block.py。第二個callback經過使用time.sleep來達到阻塞的效果。若是你運行該代碼來觀察打印信息順序時,你會發現deferred中阻塞回調仍然會阻塞掉。
總結
函數經過返回一個Deferred,向使用者暗示「我是採用異步方式的」而且當結果到來時會使用一種特殊的機制(在此處添加你的callback與errback)來得到返回結果。Defered被普遍地運用在Twisted的每一個角落,當你瀏覽Twisted源碼時你就會不停地遇到它。
4.0版本客戶端是第一個使用Deferred的Twisted版的客戶端,其使用方法爲在其異步函數中返回一個deferred來。可使用一些Twisted的APIs來使客戶端的實現更加清晰些,但我以爲它可以很好地體現出一個簡單的Twisted程序是怎麼寫的了,至少對於客戶端能夠如此確定。事實上,後面咱們會重構咱們的服務器端。
但咱們對Deferred的講解尚未結束。使用如此少許的代碼,Deferred就能提供如此之多的功能。咱們將在第9部分探討其更多的功能和功能背後的動機。