当前位置:嗨网首页>书籍在线阅读

03-延迟和延迟链

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

8.1.1 延迟和延迟链

延迟机制是Twisted提供的最基础的机制,能够帮助我们编写异步代码。Twisted API使用延迟机制,允许我们定义发生某些事件时所采取的动作序列。下面让我们具体看一下。

你可以从GitHub上获取本书的全部源代码。如果想要下载本书代码,可以使用git clone `https://github.com/ scalingexcellence/scrapybook` 。 本章的完整代码在 `ch08` 目录中,其中本示例的代码在 `ch08/deferreds.py` 文件中,你可以使用 `./deferreds.py 0` 运行该代码。

可以使用Python控制台运行如下的交互式实验。

$ python
>>> from twisted.internet import defer
>>> # Experiment 1
>>> d = defer.Deferred()
>>> d.called
False
>>> d.callback(3)
>>> d.called
True
>>> d.result
3

可以看到, Deferred 本质上代表的是一个无法立即获取的值。当触发 d 时(调用其 callback 方法),其 called 状态变为 True ,而 result 属性被设置为在回调方法中设定的值。

>>> # Experiment 2
>>> d = defer.Deferred()
>>> def foo(v):
...   print "foo called"
...   return v+1
...
>>> d.addCallback(foo)
<Deferred at 0x7f...>
>>> d.called
False
>>> d.callback(3)
foo called
>>> d.called
True
>>> d.result
4

延迟机制最强大的功能就是可以在设定值时串联其他要被调用的操作。在上面的例子中,添加了一个 foo() 函数作为 d 的回调函数。当通过调用 callback(3) 触发 d 时,会调用函数 foo() ,打印消息,并将其返回值设为 d 最终的 result 值。

>>> # Experiment 3
>>> def status(*ds):
...   return [(getattr(d, 'result', "N/A"), len(d.callbacks)) for d in 
ds]
>>> def b_callback(arg):
...   print "b_callback called with arg =", arg
...   return b
>>> def on_done(arg):
...   print "on_done called with arg =", arg
...   return arg
>>> # Experiment 3.a
>>> a = defer.Deferred()
>>> b = defer.Deferred()
>>> a.addCallback(b_callback).addCallback(on_done)
>>> status(a, b)
[('N/A', 2), ('N/A', 0)]
>>> a.callback(3)
b_callback called with arg = 3
>>> status(a, b)
[(<Deferred at 0x10e7209e0>, 1), ('N/A', 1)]
>>> b.callback(4)
on_done called with arg = 4
>>> status(a, b)
[(4, 0), (None, 0)]

该示例展示了更加复杂的延迟行为。我们看到该示例中有一个普通的延迟 a ,和之前例子中创建的一样,不过这次它有两个回调方法。第一个是 b_callback() ,返回值是另一个延迟 b ,而不是一个值。第二个是 on_done() 打印函数。我们还有一个 status() 函数,用于打印延迟状态。在两个延迟完成初始化之后,得到了相同的状态: [('N/A', 2), ('N/A', 0)] ,这意味着两个延迟都还没有被触发,并且第一个延迟有两个回调,而第二个没有回调。然后,当触发第一个延迟时,我们得到了一个奇怪的 [(<Deferred at 0x10e7209e0>, 1), ('N/A', 1)] 状态,可以看出现在 a 的值是一个延迟(实际上就是 b 延迟),并且目前它还有一个回调,这种情况是合理的,因为 b_callback() 已经被调用,只剩下了 on_done() 。意外的情况是现在 b 也包含了一个回调。实际上是在后台注册了一个回调,一旦触发 b ,就会更新它的值。当其发生时, on_done() 依然会被调用,并且最终状态会是 [(4, 0), (None, 0)] ,和我们预期的一样。

>>> # Experiment 3.b
>>> a = defer.Deferred()
>>> b = defer.Deferred()
>>> a.addCallback(b_callback).addCallback(on_done)
>>> status(a, b)
[('N/A', 2), ('N/A', 0)]
>>> b.callback(4)
>>> status(a, b)
[('N/A', 2), (4, 0)]
>>> a.callback(3)
b_callback called with arg = 3
on_done called with arg = 4
>>> status(a, b)
[(4, 0), (None, 0)]

而另一方面,如果像Experiment 3.b 所示, b 先于 a 被触发,状态将会变为 [('N/A', 2), (4, 0)] ,然后当 a 被触发时,两个回调都会被调用,最终状态与之前一样。有意思的是,不管顺序如何,最终结果都是相同的。两个例子唯一的不同是,在第一个例子中, b 值保持延迟的时间更长一些,因为它是第二个被触发的,而在第二个例子中, b 首先被触发,并且从该时刻起,它的值就会在需要时被立即使用。

此时,你应该已经对什么是延迟、它们是如何串联起来表示尚不可用的值,有了不错的理解。我们将通过第4个例子结束这一部分的研究,在该示例中,将展示如何触发依赖于多个其他延迟的方法。在Twisted的实现中,将会使用 defer.DeferredList 类。

>>> # Experiment 4
>>> deferreds = [defer.Deferred() for i in xrange(5)]
>>> join = defer.DeferredList(deferreds)
>>> join.addCallback(on_done)
>>> for i in xrange(4):
...   deferreds[i].callback(i)
>>> deferreds[4].callback(4)
on_done called with arg = [(True, 0), (True, 1), (True, 2),
              (True, 3), (True, 4)]

可以注意到,尽管 for 循环语句只触发了5个延迟中的4个, on_done() 仍然需要等到列表中所有延迟都被触发后才会调用,也就是说,要在最后的 deferreds[4].callback() 之后调用。 on_done() 的参数是一个元组组成的列表,每个元组对应一个延迟,其中包含两个元素,分别是表示成功的 True 或表示失败的 False ,以及延迟的值。