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