在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Python/ 小插曲 Deferred
小插曲 Deferred
異步編程模式與Reactor初探
使用Deferred新功能實現(xiàn)新客戶端
由twisted支持的客戶端
增強defer功能的客戶端
改進詩歌下載服務(wù)器
測試詩歌
更加"抽象"的運用Twisted
Deferred用于同步環(huán)境
輪子內(nèi)的輪子: Twisted和Erlang
Twisted 進程守護
構(gòu)造"回調(diào)"的另一種方法
Twisted 理論基礎(chǔ)
惰性不是遲緩: Twisted和Haskell
第二個小插曲,deferred
使用Deferred的詩歌下載客戶端
Deferreds 全貌
結(jié)束
取消之前的意圖
由Twisted扶持的客戶端
改進詩歌下載服務(wù)器
初識Twisted

小插曲 Deferred

回調(diào)函數(shù)的后序發(fā)展

在第六部分我們認識到這樣一個情況:回調(diào)是Twisted異步編程中的基礎(chǔ)。除了與reactor交互外,回調(diào)可以安插在任何我們寫的Twisted結(jié)構(gòu)內(nèi)。因此在使用Twisted或其它基于reactor的異步編程體系時,都意味需要將我們的代碼組織成一系列由reactor循環(huán)可以激活的回調(diào)函數(shù)鏈。

即使一個簡單的get_poetry函數(shù)都需要回調(diào),兩個回調(diào)函數(shù)中一個用于處理正常結(jié)果而另一個用于處理錯誤。作為一個Twisted程序員,我們必須充分利用這一點。應(yīng)該花點時間思考一下如何更好地使用回調(diào)及使用過程中會遇到什么困難。

分析下3.1版本中的get_poetry函數(shù):

...
def got_poem(poem):
    print poem
    reactor.stop()
def poem_failed(err):
    print >>sys.stderr, 'poem download failed'
    print >>sys.stderr, 'I am terribly sorry'
    print >>sys.stderr, 'try again later?'
    reactor.stop()

get_poetry(host, port, got_poem, poem_failed)

reactor.run()

我們想法很簡單:

  1. 如果完成詩歌下載,那么就打印它
  2. 如果沒有下載到詩歌,那就打印出錯誤信息
  3. 上面任何一種情況出現(xiàn),都要停止程序繼續(xù)運行

同步程序中處理上面的情況會采用如下方式:

...
try:
    poem = get_poetry(host, port) # the synchronous version of get_poetry
except Exception, err:
    print >>sys.stderr, 'poem download failed'
    print >>sys.stderr, 'I am terribly sorry'
    print >>sys.stderr, 'try again later?'
    sys.exit()
else:
    print poem
    sys.exit()

即callback類似else處理路徑,而errback類似except處理路徑。這意味著激活errback回調(diào)函數(shù)類似于同步程序中拋出一個異常,而激活一個callback意味著同步程序中的正常執(zhí)行路徑。 兩個版本有什么不同之外嗎?可以明確的是,在同步版本中,Python解釋器可以確保只要get_poetry拋出異常(任意類型, 這里特指Exception及其子類)就會執(zhí)行except塊。也就是說只要我們相信Python解釋器能夠正確的解釋執(zhí)行Python程序,那么就可以相信異常處理塊會在恰當(dāng)?shù)臅r間點被執(zhí)行。

和異步版本不同的是:poem_failed錯誤回調(diào)是由我們自己的代碼激活并調(diào)用的,即PoetryClientFactory的clientConnectFailed函數(shù)。是我們自己而不是Python來確保當(dāng)出錯時錯誤處理代碼能夠執(zhí)行。因此我們必須保證通過調(diào)用攜帶Failure對象的errback來處理任何可能的錯誤。

否則,我們的程序就會因為等待一個永遠不會出現(xiàn)的回調(diào)而止步不前。

這里顯示出了同步與異步版本的又一個不同之處。如果我們在同步版本中沒有使用try/except捕獲異步,那么Python解釋器會為我們捕獲然后關(guān)掉我們的程序并打印出錯誤信息。但是如果我們忘記拋出我們的異步異常(在本程序中是在PoetryClientFactory調(diào)用errback),我們的程序會一直運行下去,還開心地以為什么事都沒有呢。

顯而易見,在異步程序中處理錯誤是相當(dāng)重要的,甚至有些嚴峻。也可以說在異步程序中處理錯誤信息比處理正常的信息要重要的多,這是因為錯誤會以多種方式出現(xiàn),而正確的結(jié)果出現(xiàn)的方式是唯一的。當(dāng)使用Twisted編程時忘記處理異常是一個常犯的錯誤。

關(guān)于上面同步程序代碼的另一個默認的事實是:else與except塊兩者只能是運行其中一個(假設(shè)我們的get_poetry沒有在一個無限循環(huán)中運行)。Python解釋器不會突然決定兩者都運行或突發(fā)奇想來運行else塊27次。對于通過Python來實現(xiàn)那樣的動作是不可能的。

但在異步程序中,我們要負責(zé)callback和errback的運行。因此,我們可能就會犯這樣的錯誤:同時調(diào)用了callback與errback或激活callback27次。這對于使用get_poetry的用戶來說是不幸的。雖然在描述文檔中沒有明確地說明,像try/except塊中的else與except一樣,對于每次調(diào)用get_poetry時callback與errback只能運行其中一個,不管我們是否成功的下載完詩歌。

設(shè)想一下,我們在調(diào)試某個程序時,我們提出了三次詩歌下載請求,但是得到有7次callback被激活和2次errback被激活??赡苓@時,你會下來檢查一下,什么時候get_poetry激活了兩次callback并且還拋出一個錯誤出來。

從另一個視角來看,兩個版本都有代碼重復(fù)。異步的版本中含有兩次reactor.stop,同步版本中含有兩次sys.exit調(diào)用。我們可以重構(gòu)同步版本如下:

...
try:
    poem = get_poetry(host, port) # the synchronous version of get_poetry
except Exception, err:
    print >>sys.stderr, 'poem download failed'
    print >>sys.stderr, 'I am terribly sorry'
    print >>sys.stderr, 'try again later?'
else:
    print poem

sys.exit()

我們可以以同樣的方式來重構(gòu)異步版本嗎?說實話,確實不太可能,因為callback與errback是兩個不同的函數(shù)。難道要我們回到使用單一回調(diào)來實現(xiàn)重構(gòu)嗎?

好, 下面是我們在討論使用回調(diào)編程時的一些觀點:

  1. 激活errback是非常重要的。由于errback的功能與except塊相同,因此用戶需要確保它們的存在。他們并不是可選項,而是必選項。
  2. 不在錯誤的時間點激活回調(diào)與在正確的時間點激活回調(diào)同等重要。典型的用法是,callback與errback是互斥的即只能運行其中一個。
  3. 使用回調(diào)函數(shù)的代碼重構(gòu)起來有些困難。

后面的部分我們還會討論回調(diào),但是到這里已經(jīng)可以明白為什么Twisted引入了抽象機制(Deferred)來管理回調(diào)了。

Deferred

由于回調(diào)在異步程序中大量被使用,并且正確的使用這一機制需要一些技巧。因此,Twisted開發(fā)者設(shè)計了一種抽象機制-Deferred-讓程序員在使用回調(diào)時更加簡便。

一個Deferred有一對回調(diào)鏈,一個是為針對正確結(jié)果,另一個針對錯誤結(jié)果。新創(chuàng)建的Deferred的這兩條鏈?zhǔn)强盏?。我們可以向兩條鏈里分別添加callback與errback。其后,就可以用正確的結(jié)果或異常來激活Deferred。激活Deferred意味著以我們添加的順序激活callback或errback。圖12展示了一個擁有callback/errback鏈的Deferred對象:

http://wiki.jikexueyuan.com/project/twisted-intro/images/p07_deferred-1.png" alt="" />

由于defered中不使用reactor,所以使用它并不需要啟動事件循環(huán)。也許你在Deferred中發(fā)現(xiàn)一個setTimeout的函數(shù)中使用了reactor。放心,它已經(jīng)廢棄并且會在將來的版本中刪掉,我們可以直接無視它。

下面是我們看看第一個使用deferred的例子twisted-deferred/defer-1.py:

from twisted.internet.defer import Deferred

def got_poem(res):
    print 'Your poem is served:'
    print res

def poem_failed(err):
    print 'No poetry for you.'

d = Deferred()

# add a callback/errback pair to the chain
d.addCallbacks(got_poem, poem_failed)

# fire the chain with a normal result
d.callback('This poem is short.')

print "Finished"

代碼開始創(chuàng)建了一個新deferred,然后使用addCallbacks添加了callback/errback對,然后使用callback函數(shù)激活了其正常結(jié)果處理回調(diào)鏈。當(dāng)然了,由于只含有一個回調(diào)函數(shù)還算不上鏈,但不要緊,運行它:

Your poem is served:
This poem is short.
Finished

有幾個問題需要注意:

  1. 正如3.1版本中我們使用的callback/errback對,添加到deferred中的回調(diào)函數(shù)只攜帶一個參數(shù),正確的結(jié)果或出錯信息。其實,deferred支持回調(diào)函數(shù)可以有多個參數(shù),但至少得有一個參數(shù)并且第一個只能是正確的結(jié)果或錯誤信息。
  2. 我們向deferred添加的是回調(diào)函數(shù)對
  3. callbac函數(shù)攜帶僅有的一個參數(shù)即正確的結(jié)果來激活deferred
  4. 從打印結(jié)果順序可以看出,激活的deferred立即調(diào)用了回調(diào)。沒有任何異步的痕跡。這是因為沒有reactor參與導(dǎo)致的。

好了,讓我們來試試另外一種情況,twisted-deferred/defer-2.py激活了錯誤處理回調(diào):

from twisted.internet.defer import Deferred
from twisted.python.failure import Failure

def got_poem(res):
    print 'Your poem is served:'
    print res

def poem_failed(err):
    print 'No poetry for you.'

d = Deferred()

# add a callback/errback pair to the chain
d.addCallbacks(got_poem, poem_failed)

# fire the chain with an error result
d.errback(Failure(Exception('I have failed.')))

print "Finished"

運行它打印出的結(jié)果為:

No poetry for you.
Finished

激活errback鏈就調(diào)用errback函數(shù)而不是callback,并且傳進的參數(shù)也是錯誤信息。正如上面那樣,errback在deferred激活就被調(diào)用。

在前面的例子中,我們將一個Failure對象傳給了errback。deferred會將一個Exception對象轉(zhuǎn)換成Failure,因此我們可以這樣寫twisted-deferred/defer-3.py

from twisted.internet.defer import Deferred

def got_poem(res):
    print 'Your poem is served:'
    print res

def poem_failed(err):
    print err.__class__
    print err
    print 'No poetry for you.'

d = Deferred()

# add a callback/errback pair to the chain
d.addCallbacks(got_poem, poem_failed)

# fire the chain with an error result
d.errback(Exception('I have failed.'))

運行結(jié)果如下:

twisted.python.failure.Failure
[Failure instance: Traceback (failure with no frames): : I have failed.
]
No poetry for you.

這意味著在使用deferred時,我們可以正常地使用Exception。其中deferred會為我們完成向Failure的轉(zhuǎn)換。

下面我們來運行代碼twisted-deferred/defer4.py看看會出現(xiàn)什么結(jié)果:

from twisted.internet.defer import Deferred
def out(s): print s
d = Deferred()
d.addCallbacks(out, out)
d.callback('First result')
d.callback('Second result')
print 'Finished'

輸出結(jié)果:

First result
Traceback (most recent call last):
  ...
twisted.internet.defer.AlreadyCalledError

很意外吧,也就是說deferred不允許別人激活它兩次。這也就解決了上面出現(xiàn)的那個問題:一個激活會導(dǎo)致多個回調(diào)同時出現(xiàn)。而deferred設(shè)計機制控制住了這種可能,如果你非要在一個deferred上要激活多個回調(diào),那么正如上面那樣,會報異常錯。

這里展示了更多的例子:

  1. twisted-deferred/defer-4.py
  2. twisted-deferred/defer-5.py
  3. twisted-deferred/defer-6.py
  4. twisted-deferred/defer-7.py

那deferred能幫助我們重構(gòu)異步代碼嗎?考慮下面twisted-deferred/defer-8.py這個例子:

import sys

from twisted.internet.defer import Deferred

def got_poem(poem):
    print poem
    from twisted.internet import reactor
    reactor.stop()

def poem_failed(err):
    print >>sys.stderr, 'poem download failed'
    print >>sys.stderr, 'I am terribly sorry'
    print >>sys.stderr, 'try again later?'
    from twisted.internet import reactor
    reactor.stop()

d = Deferred()

d.addCallbacks(got_poem, poem_failed)

from twisted.internet import reactor

reactor.callWhenRunning(d.callback, 'Another short poem.')

reactor.run()

這基本上與我們上面的代碼相同,唯一不同的是加進了reactor。我們在啟動reactor后調(diào)用了callWhenRunning函數(shù)來激活deferred。我們利用了callWhenRunning函數(shù)可以接收一個額外的參數(shù)給回調(diào)函數(shù)。多數(shù)Twisted的API都以這樣的方式注冊回調(diào)函數(shù),包括向deferred添加callback的API。下面我們給deferred回調(diào)鏈添加第二個回調(diào):

import sys

from twisted.internet.defer import Deferred

def got_poem(poem):
    print poem

def poem_failed(err):
    print >>sys.stderr, 'poem download failed'
    print >>sys.stderr, 'I am terribly sorry'
    print >>sys.stderr, 'try again later?'

def poem_done(_):
    from twisted.internet import reactor
    reactor.stop()

d = Deferred()

d.addCallbacks(got_poem, poem_failed)
d.addBoth(poem_done)

from twisted.internet import reactor

reactor.callWhenRunning(d.callback, 'Another short poem.')

reactor.run()

addBoth函數(shù)向callback與errback鏈中添加了相同的回調(diào)函數(shù)。在這種方式下,deferred有可能也會執(zhí)行errback鏈中的回調(diào)。這將在下面的部分討論,只要記住后面我們還會深入討論deferred。

總結(jié)

在這部分我們分析了回調(diào)編程與其中潛藏的問題。我們也認識到了deferred是如何幫我們解決這些問題的:

  1. 我們不能忽視errback,在任何異步編程的API中都需要它。Deferred支持errbacks。
  2. 激活回調(diào)多次可能會導(dǎo)致很嚴重的問題。Deferred只能被激活一次,這就類似于同步編程中的try/except的處理方法。
  3. 含有回調(diào)的程序在重構(gòu)時相當(dāng)困難。有了deferred,我們就通過修改回調(diào)鏈來重構(gòu)程序。

關(guān)于deferred的故事還沒有結(jié)束,后面還有大量的細節(jié)來講。但對于使用它來重構(gòu)我們的客戶端已經(jīng)夠用的了,在第八部分將講述這部分內(nèi)容。

參考

本部分原作參見: dave @ http://krondo.com/?p=1682

本部分翻譯內(nèi)容參見楊曉偉的博客 http://blog.sina.com.cn/s/blog_704b6af70100q52t.html