twisted系列教程十八–异步操作的并行运行

Introduction

在上一部分我们学习了一种新的用生成器来组织一系列异步callbacks 的方法.加上deferred,我们已经有两种组织异步操作的方法了.

有时候,我们想让一组异步操作并行的运行.因为twisted 是单线程的,它不会真正的并行的运行,但是我们想要异步的I/O 在一组任务上运行的尽可能的快.比如我们的poetry client,从多个server上同时下载诗,而不是一个接一个的.这就是我们为什么用twisted.

我们的opetry client 不得不解决这个问题:你怎么知道你所有的异步操作什么时候能结束?目前为止我们是用把所有的返回结果放进一个list里面,并检查这个list 的长度.我们必须在收集结果的时候非常注意,因为一个错误的结果可能让我们的程序永久的运行下去.

就如你想象的那样,twisted 包含了一个解决这个问题的抽象,我们今天就会学习一下它的用法.

The DeferredList

DeferredList类允许我们把一个deferred 对象的列表当成一个deferred来对待.这样的话我们就可以开启多个异步的操作并在它们全部执行完的时候得到通知.让我们看一些例子.
在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 的list 创建而来.在这种情况下这个list 是空的,但是我们会看到这个list 里面的对象必须都是Deferred 对象
    DeferredList 也是一个deferred 对象,它继承至Deferred.这就意味着你可以向它加入callback 和errback,就像它是一个普通的deferred一样
    在上面的例子中,我们的callback在被我们加入之后立即触发,所以这个DeferredList一定已经立马触发了.我们过一会会继续讨论这个
    deferred list 的返回结果是一个空的list

 

现在让我们看一下 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')

现在我们创建了一个包含一个deferred 对象的DeferredList,下面是我们得到的信息:

One Deferred.
Adding Callback.
Firing d1.
We got: [(True, 'd1 result')]

一些注意的事情:

    这一次DeferredList没有触发它的callback直到我们触发了list中的deferred
    这个结果仍旧是一个list,不过这一次有了一个元素
    这个元素是一个tuple,它的第二个值是它对应的deferred 的结果

 

让我们向list中添加两个deferred,在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')]

DeferredList 的结果是一个数量和DeferredList 中deferred的数量的相同的list.结果的中的每一个元素包含了和它相对应的deferred 的返回结果,前提是这个deferred运行成功.这就意味着DeferredList不会触发直到list 中的所有中的deferred 都已经触发.一个包含空列表的DeferredList 会立即触发.
DeferredList 中deferred 运行的顺序是怎样的呢? 看一下 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')

现在我们先触发d2然后触发d1.下面是输出:

Two Deferreds.
Adding Callback.
Firing d2.
Firing d1.
We got: [(True, 'd1 result'), (True, 'd2 result')]

输出列表有着和原来的list 的一样的顺序,而不是被触发的顺序.这样非常好,因为我们可以很好的把输出结果和deferred很好的关联起来.

好的,如果DeferredList 中的deferred 有一个失败了会发生什么?输出中的那些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'))

现在我们用一个正常的结果触发d1,用一个error来触发d2.咱们先暂时忽略掉 consumeErrors 选项,下面是输出:

Firing d1.
Firing d2 with errback.
We got: [(True, 'd1 result'), (False, >)]

现在和d2 对应的返回结果出现一个错误.到现在我们应该清楚DeferredList 是怎样工作的:

    DeferredList是被一个deferred 的列表组成的
    DeferredList本身也是一个deferred,它的返回结果是一个长度和DeferredList本身长度的列表
    DeferredList在列表中所有deferred都触发之后才被触发
    返回结果列表中的每一个元素对应着DeferredList中的每一个deferred.加入那个deferred成功了 这个元素是(True,result),假如这个deferred失败了,这个元素是(False,failure)
    一个DeferredList不会失败,因为每一个deferred 无论成功与否它的结果都会被搜集到返回的列表中


下面让我们看一下consumeErrors 选项,假如我们不设置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

如果你回想一下,在deferred 中未处理的错误信息会在deferred 被垃圾回收的时候被抛出来.这个信息告诉我们我们没有全部的捕捉我们异步程序中的错误.这个错误信息从哪里来的呢?它明显的不是从DeferredList来的,所以这个错误一定来自d2.

DeferredList需要知道它下面的deferreds都在什么时候触发.DeferredList 通过增加一个callback和errback到每一个deferred,这样就可以监测了.默认的,这个callback(errback) 返回正常的结果(错误),因为返回错误会触发下一个errback,这样d2 在触发之后会保持失败状态.
但是假如我们设置consumeErrors 为True,向每一个deferred加入的errback 会返回None.我们也可以向d2加入自己的errback,例子在deferred-list/deferred-list-7.py.

Client 8.0

我们的poetry client 的8.0版本使用DeferredList去监测什么时候所有的poetry全部下载完.你可以在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())

在client 8.0 中,我们不需要poem_done callback 或者 results list.相反的,我们把从get_transformed_poem 获得的deferred 全部放入一个list,并创建一个DeferredList.因为DeferredList 会直到所有的deferred触发之后才会被触发.我们向DeferredList增加一个callback来关闭reactor.在这种情况下,我们没有使用DeferredList的返回结果,我们只需要知道什么时候所有的事情能结束.

Discussion
我们可以用图片形象话一个DeferredList 是怎样工作的,图片三十七:


图片三十七

真的很简单,仍旧有几个DeferredList 的参数我们没有覆盖掉.这些参数会改变DeferredList的默认行为,感兴趣的可以自己看.

在下一部分我们还会讲deferred 的一个特色,一个刚被twisted 10.1.0 加进去的.

你可能感兴趣的:(Python,Twisted)