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

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

小插曲 Deferred

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

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

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

分析下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()

我們想法很簡(jiǎn)單:

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

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

...
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ù)類似于同步程序中拋出一個(gè)異常,而激活一個(gè)callback意味著同步程序中的正常執(zhí)行路徑。 兩個(gè)版本有什么不同之外嗎?可以明確的是,在同步版本中,Python解釋器可以確保只要get_poetry拋出異常(任意類型, 這里特指Exception及其子類)就會(huì)執(zhí)行except塊。也就是說(shuō)只要我們相信Python解釋器能夠正確的解釋執(zhí)行Python程序,那么就可以相信異常處理塊會(huì)在恰當(dāng)?shù)臅r(shí)間點(diǎn)被執(zhí)行。

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

否則,我們的程序就會(huì)因?yàn)榈却粋€(gè)永遠(yuǎn)不會(huì)出現(xiàn)的回調(diào)而止步不前。

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

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

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

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

設(shè)想一下,我們?cè)谡{(diào)試某個(gè)程序時(shí),我們提出了三次詩(shī)歌下載請(qǐng)求,但是得到有7次callback被激活和2次errback被激活??赡苓@時(shí),你會(huì)下來(lái)檢查一下,什么時(shí)候get_poetry激活了兩次callback并且還拋出一個(gè)錯(cuò)誤出來(lái)。

從另一個(gè)視角來(lái)看,兩個(gè)版本都有代碼重復(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()

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

好, 下面是我們?cè)谟懻撌褂没卣{(diào)編程時(shí)的一些觀點(diǎn):

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

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

Deferred

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

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

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

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

下面是我們看看第一個(gè)使用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)建了一個(gè)新deferred,然后使用addCallbacks添加了callback/errback對(duì),然后使用callback函數(shù)激活了其正常結(jié)果處理回調(diào)鏈。當(dāng)然了,由于只含有一個(gè)回調(diào)函數(shù)還算不上鏈,但不要緊,運(yùn)行它:

Your poem is served:
This poem is short.
Finished

有幾個(gè)問(wèn)題需要注意:

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

好了,讓我們來(lái)試試另外一種情況,twisted-deferred/defer-2.py激活了錯(cuò)誤處理回調(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"

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

No poetry for you.
Finished

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

在前面的例子中,我們將一個(gè)Failure對(duì)象傳給了errback。deferred會(huì)將一個(gè)Exception對(duì)象轉(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.'))

運(yùn)行結(jié)果如下:

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

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

下面我們來(lái)運(yùn)行代碼twisted-deferred/defer4.py看看會(huì)出現(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

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

這里展示了更多的例子:

  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這個(gè)例子:

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()

這基本上與我們上面的代碼相同,唯一不同的是加進(jìn)了reactor。我們?cè)趩?dòng)reactor后調(diào)用了callWhenRunning函數(shù)來(lái)激活deferred。我們利用了callWhenRunning函數(shù)可以接收一個(gè)額外的參數(shù)給回調(diào)函數(shù)。多數(shù)Twisted的API都以這樣的方式注冊(cè)回調(diào)函數(shù),包括向deferred添加callback的API。下面我們給deferred回調(diào)鏈添加第二個(gè)回調(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有可能也會(huì)執(zhí)行errback鏈中的回調(diào)。這將在下面的部分討論,只要記住后面我們還會(huì)深入討論deferred。

總結(jié)

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

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

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

參考

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

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