Introduction

twisted 是一个正在发展的项目,twisted 的开发者们会添加一些新的特色或者扩展旧的.随着twisted 10.1.0 的发布,开发者们增加了一个新的功能–取消,这个就是我们今天要讲的内容.
异步的程序把request 和response 进行了解耦,于是增加了一个新的可能:在发送一个请求和得到返回结果之间你可以决定是否你还要返回结果.细想一下第十四部分的poetry proxy server.下面是这个代理怎么工作的(第一次没有缓存的时候):

一个请求进来
    这个proxy 连接真正的server去获取诗
    一但获取到完整的诗,发送给client

一切工作的很好,但是假如client在得到诗之前挂起了怎么办?这样的话我们的proxy 就会被卡住.在出现这种情况的时候我们最好的办法就是关掉连接.

回想一下图片十五,一个描述了同步程序中概念上的流程控制的图表.在这个图片中我们可以看到函数调用往下走,异常往上走.假如我们想取消一个同步的调用,程序的流程会和函数调用的方向是一样的,从高层代码到底层代码.图片三十八描述了这个过程:


图片三十八

当然,在一个同步的程序中这个是不可能的因为高层的代码不会恢复运行直到底层的代码运行完,在这时就没有什么可以取消的了.但是在一个异步的程序中,高层的代码在底层的代码运行完之前控制着程序,这就让我们有能力取消底层次的请求.

在一个twisted 程序中,底层的请求是被deferred对象所体现的.在deferred 中信息流是向下的,从底层的代码到高层的代码,这个和同步程序中return 信息流是相同的.从twsited 10.1.0 开始,高层的代码可以向底层的代码传送信息–它可以告诉底层的代码它不再想要返回的结果了.看图片三十九:


图片三十九

Canceling Deferreds

先让我们看一些例子来搞明白取消deferred 是怎样工作的.记住,运行这一部分的代码你需要将twisted 升级到10.1.0 .首先deferred-cancel/defer-cancel-1.py:

from twisted.internet import defer

def callback(res):
    print 'callback got:', res

d = defer.Deferred()
d.addCallback(callback)
d.cancel()
print 'done'

利用这个取消的特性,deferred 类增加了一个cancel 的方法.这个例子程序创建一个deferred,增加一个callback,然后在触发这个deferred之前取消它.下面是输出:

done
Unhandled error in Deferred:
Traceback (most recent call last):
Failure: twisted.internet.defer.CancelledError:

ok,取消一个deferred 触发了errback,我们正常的callback 没有被调用.并且出现的错误是twisted.internet.defer.CancelledError.让我们在deferred-cancel/defer-cancel-2.py增加一个errback:

from twisted.internet import defer

def callback(res):
    print 'callback got:', res

def errback(err):
    print 'errback got:', err

d = defer.Deferred()
d.addCallbacks(callback, errback)
d.cancel()
print 'done'

我们得到如下的输出:

errback got: [Failure instance: Traceback (failure with no frames): :
]
done

我们可以捕捉到这个CancelledError 就像捕捉其他的deferred 的错误一样.
下面让我们先触发deferred 然后再取消它,代码在deferred-cancel/defer-cancel-3.py:

from twisted.internet import defer

def callback(res):
    print 'callback got:', res

def errback(err):
    print 'errback got:', err

d = defer.Deferred()
d.addCallbacks(callback, errback)
d.callback('result')
d.cancel()
print 'done'

下面是输出:

callback got: result
done

我们的callback 被正常的触发了,然后我们的程序正常的结束.就好像cancel 没有被调用一样.看起来取消一个cancel 对一个已经触发的deferred 没有什么作用一样.(继续往下看).
如果我们取消deferred 之后再去触发deferred 会发生什么呢? 例子在deferred-cancel/defer-cancel-4.py

from twisted.internet import defer

def callback(res):
    print 'callback got:', res

def errback(err):
    print 'errback got:', err

d = defer.Deferred()
d.addCallbacks(callback, errback)
d.cancel()
d.callback('result')
print 'done'

在这种情况下我们得到如下的输出:

errback got: [Failure instance: Traceback (failure with no frames): :
]
done

很有意思,输出和第二个例子中的一样,就像根本没有触发deferred一样.所以假如deferred 已经被取消了,再去触发这个deferred 会没有什么用.但是为什么d.callback(‘result’) 会抛出一个异常,因为你不能触发一个已经被触发过的deferred?

再看一下图片三十九.触发一个deferred 是底层代码的任务,而取消一个deferred是高层代码的的动作.触发这个deferred 意味着”这里是你的结果”,而取消一个deferred 意味着”我不在需要它”.

cancel 方法主要做了两件事情:

告诉deferred 对象如果这个结果还没有来的话,我们就不要了.并忽略后面的callback或者errback
    告诉底层的正在产生结果的代码去做一些取消操作需要做的事情

我们取消deferred 的行为是自由的,在取消之后,我们就不会得到那些还没有返回的结果(即使返回了我们也收不到).但是取消deferred 并不会最终取消异步的操作.取消一个异步的操作需要一系列的操作.你可能需要关闭一个连接,回滚一些事物,杀掉一个自进程,等等. 因为deferred 只是一个callback 的组织者,它怎么知道当你取消它的时候会采取什么操作? 或者,它怎样把取消请求转发给底层的代码? 大声跟着我喊:

用Callback

Canceling Deferreds, Really

好的,让我们看一下deferred-cancel/defer-cancel-5.py:

from twisted.internet import defer

def canceller(d):
    print "I need to cancel this deferred:", d

def callback(res):
    print 'callback got:', res

def errback(err):
    print 'errback got:', err

d = defer.Deferred(canceller)
# created by lower-level code
d.addCallbacks(callback, errback)
# added by higher-level code
d.cancel()
print 'done'

这段代码很像第二个例子,除了一个多了一个canceller callback,这个callback 是deferred创建的时候传入的,不是后面加入的.这个callback 负责执行要取消这个deferred 所要进行的一系列操作(仅仅这个deferred 被取消的时候).这个canceller callback 是底层代码返回deferred 不可缺少的一部分,不是用来接收deferred并添加callback 和errback 的高层代码.

运行这个代码,你会看到如下的输出:

I need to cancel this deferred:
errback got: [Failure instance: Traceback (failure with no frames): :
]
done

你可以看到,这个canceller callback被传入一个我们想取消的deferred.在canceller callback 中我们可以进行一些取消deferred要完成的一些附属操作.canceller callback 是在errback之前触发的.实际上,这时我们可以选择用正常的结果还是错误的结果触发deferred.所有的情况在 deferred-cancel/defer-cancel-6.py 和deferred-cancel/defer-cancel-7.py.

在把reactor引入进来之前,让我们再做一个简单的测试.我们创建了一个带有canceller 的deferred,正常的触发,并取消它,你可以在deferred-cancel/defer-cancel-8.py看到代码.通过检查这个例子的输出,你可以看到在取消一个已经触发的deferred之后并没有触发canceller callback.

我们上面看的例子都没有做些实际的异步操作.下面让我们创建一个简单的触发异步操作的程序,然后我们会弄明白怎样让这个异步操作变为可以取消的.看一下代码 deferred-cancel/defer-cancel-9.py:

from twisted.internet.defer import Deferred

def send_poem(d):
    print 'Sending poem'
    d.callback('Once upon a midnight dreary')

def get_poem():
    """Return a poem 5 seconds later."""
    from twisted.internet import reactor
    d = Deferred()
    reactor.callLater(5, send_poem, d)
    return d

def got_poem(poem):
    print 'I got a poem:', poem

def poem_error(err):
    print 'get_poem failed:', err

def main():
    from twisted.internet import reactor
    reactor.callLater(10, reactor.stop)
    # stop the reactor in 10 seconds

d = get_poem()
    d.addCallbacks(got_poem, poem_error)

reactor.run()

main()

这个例子包含了一个get_poem 函数,get_poem利用callLater 方法五秒钟之后返回一首诗.main 函数调用get_poem,并添加了一个callback/errback 对,并启动reactor.
运行这个例子会有如下的输出:

Sending poem
I got a poem: Once upon a midnight dreary

让我们试着在这首诗返回之前取消deferred.我们加上一些代码在两秒钟之后取消deferred.:

reactor.callLater(2, d.cancel) # cancel after 2 seconds

完整的程序在deferred-cancel/defer-cancel-10.py,会输出如下的内容:

get_poem failed: [Failure instance: Traceback (failure with no frames): :
]
Sending poem

这个例子清晰的描述了取消一个deferred并不一定会取消底层的异步请求.在两秒之后我们从errback看到了输出了CancelledError,在五秒之后我们仍旧会看到send_poem 的输出(但是接下来的callback没有触发).

这和例子四deferred-cancel/defer-cancel-4.py.取消deferred 导致最后的结果被忽略,但是真正意义上说并没有完全终止操作.根据我们上面讲的,要想彻底取消一个deferred你必须在deferred 创建的时候向其传入一个cancel callback.

这个被传入的callback需要做些什么呢?看一下callLater 的手册.callLater 返回的值是一个实现了IDelayedCall,并带有一个cancel 方法的对象,这个cancel 方法可以用来组织延迟的call 被执行.

这样就比较简单了,更新后的代码在deferred-cancel/defer-cancel-11.py,主要的变化在get_poem 函数:

def get_poem():
    """Return a poem 5 seconds later."""

def canceler(d):
        # They don't want the poem anymore,
        # so cancel the delayed call
        delayed_call.cancel()

# At this point we have three choices:
        # 1. Do nothing, and the deferred will fire the errback
        # chain with CancelledError.
        # 2. Fire the errback chain with a different error.
        # 3. Fire the callback chain with an alternative result.

d = Deferred(canceler)

from twisted.internet import reactor
    delayed_call = reactor.callLater(5, send_poem, d)

return d

在这个新的版本中,我们保存了从callLater 返回的值从而让我们可以在callback中用到.我们的callback唯一需要做的是触发delayed_call.cancel().根据我们上面讲的,我们也可以选择我们自己去触发这个callback.最新版的例子的输出为:

get_poem failed: [Failure instance: Traceback (failure with no frames): :
]

你可以看到,deferred 被取消了,异步的操作确实被抛弃了.

Poetry Proxy 3.0

根据我们所讲的,poetry proxy server 是一个应用cancel deferred 的很好的场景,因为它可以允许我们放弃诗的下载假如没有人想要诗的话. proxy Version 3.0 代码在 twisted-server-4/poetry-proxy.py,实现了deferred 的取消.第一个变化的在 PoetryProxyProtocol:

class PoetryProxyProtocol(Protocol):

def connectionMade(self):
        self.deferred = self.factory.service.get_poem()
        self.deferred.addCallback(self.transport.write)
        self.deferred.addBoth(lambda r: self.transport.loseConnection())

def connectionLost(self, reason):
        if self.deferred is not None:
            deferred, self.deferred = self.deferred, None
            deferred.cancel() # cancel the deferred if it hasn't fired

和老一版本的相比.主要的变化是:

保存我们从get_poem 得到的deferred,以便我们可以以后用于取消deferred
    当连接关闭的时候取消deferred.需要注意的是在我们实际上获取诗之后也会取消deferred,但是取消一个已经触发的deferred不会有什么影响

现在我们需要确认的是取消deferred 之后确实会放弃下载诗.为了这个我们也需要改变一下 ProxyService:

class ProxyService(object):

poem = None # the cached poem

def __init__(self, host, port):
        self.host = host
        self.port = port

def get_poem(self):
        if self.poem is not None:
            print 'Using cached poem.'
            # return an already-fired deferred
            return succeed(self.poem)

def canceler(d):
            print 'Canceling poem download.'
            factory.deferred = None
            connector.disconnect()

print 'Fetching poem from server.'
        deferred = Deferred(canceler)
        deferred.addCallback(self.set_poem)
        factory = PoetryClientFactory(deferred)
        from twisted.internet import reactor
        connector = reactor.connectTCP(self.host, self.port, factory)
        return factory.deferred

def set_poem(self, poem):
        self.poem = poem
        return poem

和老版本的ProxyService,有如下变化:

我们保存了reactor.connectTCP 的返回值,它是一个IConnector 对象,我们可以用它提供的disconnect方法来关闭连接
    我们创建了一个带有canceler callback 的deferred.这个callback 用connector 来关闭连接.但首先要先设置factory.deferred 属性为None.否则的话,这个factory可能会在这个deferred 被CancelledError触发之前触发这个deferred(发生连接错误的时候)

你可能已经注意到我们现在在ProxyService 创建这个deferred,而不是在PoetryClientFactory中.因为我们的canceler callback 需要操作IConnector 对象,ProxyService 就成了创建deferred 最方便的额地方.

在我们之前的的例子中,我们的canceler callback 都是作为一个闭包来实现的.闭包是非常有用的当我们要取消callback的时候.

让我们试一下我们的proxy.首先开启一个slow server.它需要足够慢让我们有时间来取消:

python blocking-server/slowpoetry.py --port 10001 poetry/fascination.txt

现在我们开启proxy:

python twisted-server-4/poetry-proxy.py --port 10000 10001

现在我们可以从proxy 下载诗了,可以用curl:

curl localhost:10000

几秒之后,按下Ctrl-C停止client,在运行proxy 的终端中你可以看到如下的输出:

Fetching poem from server.
Canceling poem download.

你可以看到我们的server 已经停止输出了,因为我们的proxy已经挂起了.

One More Wrinkle

我们上面已经说过了取消一个已经触发过的deferred 会没有作用.这并不是十分准确.在第十三部分我们了解到被添加到deferred 上的callback 和errback 会返回deferred.在这种情况下,外部的deferred 暂停执行它的cllback 链并等待内部的deferred 触发.

因此,即使一个deferred 触发了发起异步请求的高层的代码也不会接收到结果.因为这个callback 正在等待内部的deferred 实行完毕.如果高层的代码取消了外部的deferred会发生什么? 在这种情况下外部的deffered 不会取消自己,相反的它会取消内部的deferred.

所以当你取消一个deferred 的时候,你不是在取消主要的异步操作,而是在取消其他的被主要的异步操作触发的异步操作.(比较难懂哈 ,建议看英文).

我们可以用一个例子来描述这个过程.代码在deferred-cancel/defer-cancel-12.py:

from twisted.internet import defer

def cancel_outer(d):
    print "outer cancel callback."

def cancel_inner(d):
    print "inner cancel callback."

def first_outer_callback(res):
    print 'first outer callback, returning inner deferred'
    return inner_d

def second_outer_callback(res):
    print 'second outer callback got:', res

def outer_errback(err):
    print 'outer errback got:', err

outer_d = defer.Deferred(cancel_outer)
inner_d = defer.Deferred(cancel_inner)

outer_d.addCallback(first_outer_callback)
outer_d.addCallbacks(second_outer_callback, outer_errback)

outer_d.callback('result')

# at this point the outer deferred has fired, but is paused
# on the inner deferred.

print 'canceling outer deferred.'
outer_d.cancel()

print 'done'

在这个例子中我们创造了两个deferred,一个内部的一个外部的.首先我们触发外部的deferred,然后取消它.这个例子会有如下输出:

first outer callback, returning inner deferred
canceling outer deferred.
inner cancel callback.
outer errback got: [Failure instance: Traceback (failure with no frames): :
]
done

你可以看到,取消外部的deferred 不会引起外部的cancel callback 触发.相反的,它取消内部的deferred,所以内部deferred 的cancel callback会触发,然后外部的errback 会接收到一个CancelledError.

Discussion

取消一个deferred 是一个非常有用的操作,允许我们的程序去避免无谓的工作.但是也有一些取巧.

一个非常重要的要记住的事实是取消一个异步操作并不会一定取消根本的异步操作.实际上,大多数deferred 并不会真正的取消,因为大多数的twisted 代码还没有更新.检查文档或者源代码去看看是否取消deferred 会不会真正取消请求,或者只是忽略它.

第二个重要的事实是仅仅从异步的api 中返回一个deferred 并不会真正的取消deferred.假如你想在你的代码中实现取消deferred,你需要在在源代码中找更多的例子.

twisted系列教程十九–cancel deferred相关推荐

  1. twisted系列教程十四— pre-fireed deferred

    Introduction 在这一部分我们将要学习deferred 类的另外的一个方面.为了促进讨论,我们要为我们的poetry service增加一个server.假设我们有大量的内部的client ...

  2. PVE系列教程(十九)、ubuntu22.04使用Nginx配置chevereto服务器

    PVE系列教程(十九).ubuntu22.04使用Nginx配置chevereto服务器 为了更好的浏览体验,欢迎光顾勤奋的凯尔森同学个人博客http://www.huerpu.cc:7000 一.环 ...

  3. ComicEnhancerPro 系列教程十九:用JpegQuality看JPG文件的压缩参数

    作者:马健 邮箱:stronghorse_mj@hotmail.com 主页:http://www.comicer.com/stronghorse/ 发布:2017.07.23 教程十九:用JpegQ ...

  4. twisted系列教程十–可以变化的诗

    Client 5.0 现在我们将要想我们的client中加入一些变形逻辑.但是首先我不得不说:我不知道怎样写一个Byronification 引擎,它超出我的能力范围了.做为替代,我会实现一个相对简单 ...

  5. twisted系列教程十五–测试twisted代码

    Introduction 在这个系列中我们也已经写了很多twisted 代码了,但目前为止我们忽略了一个很重要的事情-测试.你可能也一直在想我们怎样用一个同步的测试框架unitest来测试我们的异步的 ...

  6. twisted系列教程十六–twisted守护进程

    Introduction 到目前为止我们写的server 还运行在一个终端里面,通过print 语句向外输出内容.开发的时候这样做是很有好处的,但是当你部署一个产品的时候这样就不好了.一个生产环境中的 ...

  7. twisted系列教程十二–为server 增加一个service

    One More Server 在第九部分和第十部分我们介绍了关于诗歌的变形引擎的想法,最后我们实现了cummingsifier,我们还让它抛出随机的异常来模拟错误.但是假如这个变形的引擎在另外一台服 ...

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

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

  9. ComicEnhancerPro 系列教程十八:JPG文件长度与质量

    作者:马健 邮箱:stronghorse_mj@hotmail.com 主页:http://www.comicer.com/stronghorse/ 发布:2017.07.23 教程十八:JPG文件长 ...

最新文章

  1. 【c++】15.订阅消息后转化为proto消息,再序列化,最后通过tcp发送出去
  2. linux查看指定用户的所有进程
  3. 666A-Reberland Linguistics(动态规划)
  4. vb整合多个excel表格到一张_[Excel]同一工作簿中多个工作表保存成独立的表格
  5. ant design vue 树形控件_官宣!vue.ant.design 低调上线
  6. 安卓案例:利用下拉列表选择科目
  7. 常用的Oracle命令整理
  8. java-第五章-while=计算1~50中是7的被耍的数值之和
  9. 软件工程网络15个人阅读作业2(201521123010徐璐琳)
  10. 域内,如何限制一台电脑只能指定的域用户登录
  11. Linux系统p4vasp使用教程,[分享]一个详细的p4vasp安装指南 - 计算模拟 - 小木虫 - 学术 科研 互动社区...
  12. 二叉树入门OJ—递归思想练习
  13. 2020计算机应用基础终结性考试,2019-2020年电大考试《计算机应用基础》形成性考核.docx-文档在线预览...
  14. 双11快速拉新促活,容联云智能客服助力商家提升GMV
  15. Vue 祖孙方法调用 祖父级方法在孙级调用 祖孙传参
  16. 职场必知的十条“钻石”心态
  17. cmd中回退到上一级文件目录 与 定位下一级目录
  18. C++中两个unsigned型数值相减
  19. Android内存泄漏情况总结
  20. 操作符精讲——这些操作符你还记得几个?

热门文章

  1. fcpx插件:21个模拟相机取景器数码屏显效果预设Camera Rec
  2. fcpx大胆流行标题插件 Bold Pop for mac
  3. P2415 集合求和(python3实现)
  4. java初始化数据报_java – 如何在Docker中初始化数据库后启动flyway
  5. python读取hadoop库数据_使用Python访问HDFS
  6. C++笔记-Stack around the variable问题解析
  7. 网站建设:部署与发布
  8. HTML作业-保护环境-保护地球
  9. 机器手六维坐标怎么定义_机器人学——2.4-坐标系的旋转和运动增量
  10. android qq弹出菜单,Android开发实现qqminihd 左右滑动菜单效果