在上一個(gè)部分,我們學(xué)習(xí)了使用生成器構(gòu)造順序異步回調(diào)的新方法, 加上 deferreds
,我們現(xiàn)在有兩種將異步操作鏈接在一起的方法.
但是有時(shí)我們需要"并行"的運(yùn)行一組異步操作.由于Twisted是單線程的,它實(shí)際并不會(huì)并發(fā)運(yùn)行,但我們希望使用異步I/O在一組任務(wù)上盡可能快的工作.以我們的詩(shī)歌客戶端為例,它從多個(gè)服務(wù)器同時(shí)下載詩(shī)歌,而不是一個(gè)接一個(gè)的方式.這就是使用Twisted下載詩(shī)歌的全部細(xì)節(jié).
因此所有詩(shī)歌客戶端需要解決這樣一個(gè)問(wèn)題:怎樣得知你啟動(dòng)的所有異步操作都已經(jīng)完成?目前我們通過(guò)將結(jié)果匯總到一個(gè)列表(如客戶端 7.0中的 結(jié)果 列表)并檢查這個(gè)列表的長(zhǎng)度來(lái)解決這個(gè)問(wèn)題.除了收集成功的結(jié)果,我們還必須小心地對(duì)待失敗,否則一個(gè)失敗將使程序以為還有工作需要做而進(jìn)入死循環(huán).
正如你所料,Twisted包含一個(gè)抽象層可以用來(lái)解決這個(gè)問(wèn)題,我們來(lái)看一看.
DeferredList
類使我們可以將一個(gè) defered
對(duì)象列表視為一個(gè) defered
對(duì)象.通過(guò)這種方法我們啟動(dòng)一族異步操作并且在它們?nèi)客瓿珊螳@得通知(無(wú)論它們成功或者失敗).讓我們看一些例子.
在 deferred-list/deferred-list-1.py 中,可以找到如下代碼:
from twisted.internet import defer
def got_results(res):
print 'We got:', res
print 'Empty List.'
d = defer.DeferredList([])
print 'Adding Callback.'
d.addCallback(got_results)
如果運(yùn)行它,將得到如下輸出:
Empty List.
Adding Callback.
We got: []
注意以下幾點(diǎn):
DeferredList
由一個(gè)Python列表初始化創(chuàng)建而成.在這種情況下,列表是空的,但我們很快將看到列表的元素必須是 Deferred
對(duì)象.DeferredList
本身是一個(gè) deferred
(它繼承 Deferred
).這意味著你可以像對(duì)待普通 deferred
一樣向其添加回調(diào)和錯(cuò)誤回調(diào).DeferredList
也必須立即激發(fā).我們一會(huì)兒再討論.deferred
列表的結(jié)果本身也是一個(gè)列表(空).下面看一下 deferred-list/deferred-list-2.py:
from twisted.internet import defer
def got_results(res):
print 'We got:', res
print 'One Deferred.'
d1 = defer.Deferred()
d = defer.DeferredList([d1])
print 'Adding Callback.'
d.addCallback(got_results)
print 'Firing d1.'
d1.callback('d1 result')
現(xiàn)在我們創(chuàng)建了包含一個(gè) deferred
元素的 DeferredList
列表,得到如下輸出:
One Deferred.
Adding Callback.
Firing d1.
We got: [(True, 'd1 result')]
注意以下幾點(diǎn):
DeferredList
沒(méi)有激發(fā)它的回調(diào),直到我們激發(fā)列表中的 deferred
.deferred
的結(jié)果.讓我們向列表添加兩個(gè) deferreds
(deferred-list/deferred-list-3.py):
from twisted.internet import defer
def got_results(res):
print 'We got:', res
print 'Two Deferreds.'
d1 = defer.Deferred()
d2 = defer.Deferred()
d = defer.DeferredList([d1, d2])
print 'Adding Callback.'
d.addCallback(got_results)
print 'Firing d1.'
d1.callback('d1 result')
print 'Firing d2.'
d2.callback('d2 result')
得到如下輸出:
Two Deferreds.
Adding Callback.
Firing d1.
Firing d2.
We got: [(True, 'd1 result'), (True, 'd2 result')]
現(xiàn)在 DeferredList
的結(jié)果非常清晰,至少以我們的使用方式,它是一個(gè)列表,元素個(gè)數(shù)與傳入構(gòu)造器的 deferred
列表元素個(gè)數(shù)相同. 而且結(jié)果列表的元素包含原始的 deferreds
結(jié)果信息,至少當(dāng)這些 deferred
成功返回.這意味著 DeferredList
本身并不激發(fā)直到所有的原始列表中的 deferreds
都被激發(fā). 而且以一個(gè)空列表創(chuàng)建的 DeferredList
會(huì)立即激發(fā),因?yàn)樗恍枰却魏?deferreds
.
那么最終結(jié)果列表中的元素順序如何? 考慮以下代碼( deferred-list/deferred-list-4.py):
from twisted.internet import defer
def got_results(res):
print 'We got:', res
print 'Two Deferreds.'
d1 = defer.Deferred()
d2 = defer.Deferred()
d = defer.DeferredList([d1, d2])
print 'Adding Callback.'
d.addCallback(got_results)
print 'Firing d2.'
d2.callback('d2 result')
print 'Firing d1.'
d1.callback('d1 result')
這里我們先激發(fā) d2
然后再激發(fā) d1
,注意構(gòu)造參數(shù)中的 deferred
列表里 d1
, d2
仍是原先的順序.輸出結(jié)果如下:
Two Deferreds.
Adding Callback.
Firing d2.
Firing d1.
We got: [(True, 'd1 result'), (True, 'd2 result')]
輸出列表中結(jié)果的順序與原始 deferred
列表順序相對(duì)應(yīng),而不是 deferred
碰巧被激發(fā)的順序.這一點(diǎn)非常好,因?yàn)槲覀兛梢院苋菀椎貙⒚總€(gè)結(jié)果與生成它的相應(yīng)的操作聯(lián)系在一起(如哪首詩(shī)來(lái)自哪個(gè)服務(wù)器).
好了,那如果列表中一個(gè)或多個(gè) deferreds
失敗了怎么辦呢? 上面結(jié)果中的 True
有什么用? 再看一個(gè)例子(deferred-list/deferred-list-5.py):
from twisted.internet import defer
def got_results(res):
print 'We got:', res
d1 = defer.Deferred()
d2 = defer.Deferred()
d = defer.DeferredList([d1, d2], consumeErrors=True)
d.addCallback(got_results)
print 'Firing d1.'
d1.callback('d1 result')
print 'Firing d2 with errback.'
d2.errback(Exception('d2 failure'))
現(xiàn)在我們以正常結(jié)果激發(fā) d1
,以錯(cuò)誤激發(fā) d2
.先暫時(shí)忽略 consumerErrors
選項(xiàng),稍候介紹.這里是輸出結(jié)果:
Firing d1.
Firing d2 with errback.
We got: [(True, 'd1 result'), (False, <twisted.python.failure.Failure <type 'exceptions.Exception'>>)]
這次對(duì)應(yīng) d2
的元組在第二個(gè)位置出現(xiàn)了一個(gè) Failure
,并且第一個(gè)位置是 False
.至此 DeferredList
的工作原理非常清晰(但繼續(xù)瀏覽以下討論):
DeferredList
是以一個(gè) deferred
對(duì)象列表創(chuàng)建的.DeferredList
本身是一個(gè) deferred
,它返回的結(jié)果是一個(gè)列表,長(zhǎng)度與 deferred
列表相同.deferred
被激發(fā)后, DeferredList
將會(huì)被激發(fā).deferred
.如果某個(gè) deferred
成功返回,相應(yīng)元素是(True
,result),如果失敗則為(False
,failure).DeferredList
不會(huì)失敗,因?yàn)闊o(wú)論每個(gè) deferred
的返回結(jié)果是什么都會(huì)被集總到結(jié)果列表中(同樣,請(qǐng)看下面討論).現(xiàn)在讓我們討論一下被傳入 DeferredList
的 consumeErrors
選項(xiàng),如果我們運(yùn)行以上相同代碼而不傳入此選項(xiàng)(deferred-list/deferred-list-6.py),則得到以下輸出:
Firing d1.
Firing d2 with errback.
We got: [(True, 'd1 result'), (False, >twisted.python.failure.Failure >type 'exceptions.Exception'<<)]
Unhandled error in Deferred:
Traceback (most recent call last):
Failure: exceptions.Exception: d2 failure
如果你還記得,"Unhandled error in Deferred"消息是在 deferred
垃圾回收時(shí)被生成的,而且它表示最后一個(gè)回調(diào)失敗了.這個(gè)消息告訴我們并沒(méi)有完全捕獲潛在的異步錯(cuò)誤.在我們例子中,它是從哪里來(lái)的呢? 很明顯不是來(lái)自 DeferredList
,因?yàn)樗呀?jīng)成功返回了.所以它一定是來(lái)自 d2
.
DeferredList
需要知道它所監(jiān)視的 deferred
何時(shí)激發(fā). DeferredList
以通常的方式向每個(gè) deferred
添加一個(gè)回調(diào)和錯(cuò)誤回調(diào). 默認(rèn)地,這個(gè)回調(diào)(或錯(cuò)誤)返回原始結(jié)果(或錯(cuò)誤)在將它們放入最終結(jié)果列表之后.由于錯(cuò)誤回調(diào)返回原始 failure
后將觸發(fā)下一個(gè)錯(cuò)誤回調(diào), d2
在它被激發(fā)后仍然保持失敗狀態(tài).
但是如果我們將 consumeErrors=True
傳遞給 DeferredList
, 它將向每個(gè) deferred
添加返回 None
的錯(cuò)誤回調(diào), 即"消耗"掉這個(gè)錯(cuò)誤并且取消警告信息. 我們同樣可以向 d2
添加自己的錯(cuò)誤回調(diào)來(lái)處理錯(cuò)誤,如 deferred-list/deferred-list-7.py.
獲取詩(shī)歌客戶端8.0發(fā)布啦!客戶端使用 DeferredList
去發(fā)現(xiàn)所有詩(shī)歌何時(shí)完成(或失敗).新版客戶端位于 twisted-client-8/get-poetry.py. 同樣,唯一的變化在于 poetry_main, 我們來(lái)看一下重要的變化:
...
ds = []
for (host, port) in addresses:
d = get_transformed_poem(host, port)
d.addCallbacks(got_poem)
ds.append(d)
dlist = defer.DeferredList(ds, consumeErrors=True)
dlist.addCallback(lambda res : reactor.stop())
你可以與 客戶端 7.0 中的相應(yīng)部分比較.
在客戶端 8.0中,我們不需要 poem_done
回調(diào)和 results
列表.相反,我們把每個(gè)從 get_transformed_poem
返回的 deferred
放入 ds
列表,之后創(chuàng)建一個(gè) DeferredList
.由于 DeferredList
不會(huì)在所有詩(shī)歌完成或失敗之前激發(fā),我們僅僅向 DeferredList
添加一個(gè)回調(diào)以便關(guān)閉 reactor
. 在我們這個(gè)情況中,沒(méi)有使用 DeferredList
返回的結(jié)果,我們僅僅需要知道所有事情何時(shí)結(jié)束.僅此而已!
可視化 DeferredList
的工作方式:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p18_deferred-list.png" alt="" /> 圖33 DeferredList 的結(jié)果
非常簡(jiǎn)單,真的. 還有一些關(guān)于 DeferredList
的選項(xiàng)我們沒(méi)有涉及,以及那些改變我們以上所描述行為的選項(xiàng).我們?cè)趨⒖季毩?xí)中把這些留給讀者自己探索.
在第十九節(jié)中我們將進(jìn)一步介紹 Deferred
類, 包括 Twisted 10.1.0 提出的最新特性.
DeferredList
的源代碼.deferred-list
中的例子去實(shí)現(xiàn)可選的構(gòu)造器參數(shù) fireOnOneCallback
和 fireOnOneErrback
. 實(shí)現(xiàn)你將用其中一個(gè)(或兩個(gè)都使用)的情景.DeferredLists
列表創(chuàng)建一個(gè) DeferredList
嗎? 如果是這樣,結(jié)果將是什么?DeferredList
的結(jié)果.DeferredDict
的句法并且實(shí)現(xiàn)它.本部分原作參見(jiàn): dave @ http://krondo.com/blog/?p=2571
本部分翻譯內(nèi)容參見(jiàn)luocheng @ https://github.com/luocheng/twisted-intro-cn/blob/master/p18.rst