我們已經(jīng)對(duì)deferreds有些理解了,現(xiàn)在我們可以使用它重寫我們的客戶端。你可以在twisted-client-4/get-poetry.py中看到它的實(shí)現(xiàn)。
這里的get_poetry已經(jīng)再也不需要callback與errback參數(shù)了。相反,返回了一個(gè)用戶可能根據(jù)需要添加callbacks和errbacks的新deferred。
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
這里的工廠使用一個(gè)deferred而不是callback/errback來初始化。一旦我們獲取到poem或者沒有連接到服務(wù)器,deferred就會(huì)以返回一首詩歌或一個(gè)failure的方式被激活。
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)
注意我們?cè)赿eferred被激活后是如何銷毀其引用的。這種方式普便存在于Twisted的源代碼中,這樣做可以保證我們不會(huì)激活一個(gè)deferred兩次。這也為Python的垃圾回收帶來了方便。
這里仍然不用去改變poetryProtocol。我們只需要更新poetry_main函數(shù)即可:
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的回調(diào)鏈特性,從先前的callback與errback回調(diào)中,重構(gòu)出poem_done調(diào)用的。
由于deferred在Twisted大量被使用,使用小寫字母d來表示當(dāng)前正在工作中的deferred已經(jīng)成為慣例。
新版本的客戶端與我們前面的同步版本的客戶端一樣,get_poetry得到的參數(shù)都是詩歌下載服務(wù)器的地址。同步版本返回的是詩歌內(nèi)容,而異步版本返回的卻是一個(gè)deferred。返回一個(gè)deferred是Twisted的APIs或用Twisted寫的程序常見的,這樣一來我們可以這樣來理解deferred:
一個(gè)Deferred代表了一個(gè)"異步的結(jié)果"或者"結(jié)果還沒有到來"
在圖13中可以更加清晰地表達(dá)出兩者之間的不同:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p08_sync-async.png" alt="" />
異步函數(shù)返回一個(gè)deferred,對(duì)用戶意味著:
我是一個(gè)異步函數(shù),不管你想要什么,可能現(xiàn)在馬上得不到,但當(dāng)結(jié)果來到時(shí),我會(huì)激活這個(gè)deferred的callback鏈并返回結(jié)果;或者當(dāng)出錯(cuò)時(shí),相應(yīng)地激活errback鏈并返回出錯(cuò)信息。
當(dāng)然,這個(gè)函數(shù)是不能隨意激活這個(gè)deferred的,因?yàn)樗呀?jīng)返回了。但這個(gè)函數(shù)已經(jīng)啟動(dòng)了一系列事件,這些事件最終將會(huì)激活這個(gè)deferred。
因此,deferred是為適應(yīng)異步模式的一種延遲函數(shù)返回的方式。函數(shù)返回一個(gè)deferred意味著其是異步的,代表著將來的結(jié)果,也是對(duì)將來能夠返回結(jié)果的一種承諾。
同步函數(shù)也能返回一個(gè)deferred,因此嚴(yán)格來說,返回deferred只能說可能是異步的。我們會(huì)在將來的例子中會(huì)看到同步函數(shù)返回deferred。
由于deferred的行為已經(jīng)很好的定義與理解,因此在實(shí)現(xiàn)自己的API時(shí)返回一個(gè)deferred更容易讓其它的Twisted程序理解你的代碼。如果沒有deferred,可能每個(gè)人寫的模塊都使用不同的方式來處理回調(diào),如果這樣就增加了相互理解的工作量。
當(dāng)你使用Deferred時(shí),你仍然在使用回調(diào),它們?nèi)匀挥蓃eactor來調(diào)用。
當(dāng)首次學(xué)習(xí)Twisted時(shí),經(jīng)常犯的一個(gè)錯(cuò)誤就是:會(huì)給deferred增加一些它本身不能實(shí)現(xiàn)的功能。尤其是:經(jīng)常假設(shè)在deferred上添加一個(gè)函數(shù)就可以使其變成異步函數(shù)。這可能會(huì)讓你產(chǎn)生這樣的想法:在Twisted 中可以通過將os.system的函數(shù)添加到deferred的回調(diào)鏈中。
我認(rèn)為,這可能是沒有弄清楚異步編程的原因才產(chǎn)生這樣的想法。由于Twisted代碼使用了大量的deferred但卻很少會(huì)涉及到reactor,可能會(huì)認(rèn)為deferred做了大部分工作。如果你是從開始閱讀這個(gè)系列的,你就會(huì)知道事情遠(yuǎn)不是這樣。雖然Twisted是由眾多部分組合在一起來工作的,但實(shí)現(xiàn)異步的主要工作都是由reactor來完成的。Deferred是一個(gè)很好的抽象概念,但前面幾個(gè)例子中的客戶端我們卻沒有使用它,而reactor卻都用到了。
來看看我們第一個(gè)回調(diào)激活時(shí)的跟蹤棧信息。運(yùn)行twisted-client-4/get-poetry-stack.py讓其連接你打開的服務(wù)器:
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可以很好地說明具體的調(diào)用關(guān)系:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p08_reactor-deferred-callback.png" alt="" />
這很類似于我們前面的Twisted客戶端,雖然這張圖的調(diào)用關(guān)系并不清晰而會(huì)讓你摸不著頭腦。但我們先不深入分析這張圖。有一個(gè)細(xì)節(jié)并沒有在這張圖上反映出來:callback鏈直到第二個(gè)回調(diào)poem_done激活前才將控制權(quán)還給reactor。
通過使用deferred,我們?cè)谟蒚wisted中的reactor啟動(dòng)的回調(diào)中加入了一些自己的東西,但我們并沒有改變異步程序的基礎(chǔ)架構(gòu)。回憶下回調(diào)編程的特點(diǎn):
在一個(gè) deferred上追加一個(gè)回調(diào)并不會(huì)改變上面這些實(shí)事。尤其是,第4 條。因此當(dāng)一個(gè)deferred激活時(shí)被阻塞,那么整個(gè)Twisted就會(huì)陷入阻塞中。因此我們會(huì)得到如下結(jié)論:
Deferred只是解決回調(diào)函數(shù)管理問題的一種解決方案,它并不替代回調(diào)方式,也不能將阻塞式的回調(diào)變成非阻塞式回調(diào)的。
我通過構(gòu)建一個(gè)添加阻塞式回調(diào)的deferred來驗(yàn)證最后一點(diǎn)。驗(yàn)證代碼文件為twisted-deferred/defer-block.py。第二個(gè)callback通過使用time.sleep來達(dá)到阻塞的效果。如果你運(yùn)行該代碼來觀察打印信息順序時(shí),你會(huì)發(fā)現(xiàn)deferred中阻塞回調(diào)仍然會(huì)是阻塞的。
函數(shù)通過返回一個(gè)Deferred,向使用者暗示"我是采用異步方式的"并且當(dāng)結(jié)果到來時(shí)會(huì)使用一種特殊的機(jī)制(在此處添加你的callback與errback)來獲得返回結(jié)果。Defered被廣泛地運(yùn)用在Twisted的每個(gè)角落,當(dāng)你瀏覽Twisted源碼時(shí)你就會(huì)不停地遇到它。
4.0版本客戶端是第一個(gè)使用Deferred的Twisted版的客戶端,其使用方法為在其異步函數(shù)中返回一個(gè)deferred??梢允褂靡恍㏕wisted的APIs來使客戶端的實(shí)現(xiàn)更加清晰些,但我覺得它能夠很好地體現(xiàn)出一個(gè)簡(jiǎn)單的Twisted程序是怎么寫的了,至少對(duì)于客戶端可以如此肯定。事實(shí)上,后面我們會(huì)重構(gòu)我們的服務(wù)器端。
但我們對(duì)Deferred的講解還沒有結(jié)束。使用如此少量的代碼,Deferred就能提供如此之多的功能。我們將在第9部分探討其更多的功能和功能背后的動(dòng)機(jī)。
本部分原作參見: dave @ http://krondo.com/?p=1778
本部分翻譯內(nèi)容參見楊曉偉的博客 http://blog.sina.com.cn/s/blog_704b6af70100q6oi.html