对twisted诗歌服务器的总结和笔记

差不多两个月之前的时候看过一段时间的twisted源码和诗歌服务器的教程,但是当时的笔记都记在笔记本,两个月之后想要再用的时候印象又已经模糊了。况且当时对于事件驱动和异步回调的理解没有现在深,系统地看一遍教程记一下twisted和defer怎样一步步产生的。个人感觉这个比当时看tcp差错控制的演进还是要简单一点。。
第一步,用2to3 -w 将所有代码转换成python3格式

Twisted属于单reactor单线程模型

1.Twisted 理论基础
首先介绍了一下多线程和异步模型
在多线程程序中,对于停止某个线程启动另外一个线程,其决定权并不在程序员手里而在操作系统那里,因此,程序员在编写程序过程中必须要假设在任何时候一个线程都有可能被停止而启动另外一个线程。相反,在异步模型中,一个任务要想运行必须显式放弃当前运行的任务的控制权。这也是相比多线程模型来说,最简洁的地方。

个人理解:简洁的意思是如果你能手动控制当前任务放弃控制权,能避免一些糟糕的情况(比如说死锁?消费者生产者问题。。)。虽然这里看起来需要手动添加到哪个位置时防止,增加了代码量,但是有效避免出错及为了处理出错而增加的代码量。(多线程并不适合逻辑复杂的任务)(协程与异步,协程既有异步又有同步)

异步模型要比同步模型复杂得多。程序员必须将任务组织成序列来交替的小步完成。因此,若其中一个任务用到另外一个任务的输出,则依赖的任务(即接收输出的任务)需要被设计成为要接收系列比特或分片而不是一下全部接收。

因此,就要问了,为什么还要使用异步模型呢? 在这儿,我们至少有两个原因。首先,如果有一到两个任务需要完成面向人的接口,如果交替执行这些任务,系统在保持对用户响应的同时在后台执行其它的任务。因此,虽然后台的任务可能不会运行的更快,但这样的系统可能会受欢迎的多。
然而,有一种情况下,异步模型的性能会高于同步模型,有时甚至会非常突出,即在比较短的时间内完成所有的任务。这种情况就是任务被强行等待或阻塞,如图4所示:

据我了解,第一种情况的典型代表就是图形界面,其中事件驱动是很广泛使用的设计模式,第二种的代表就是爬虫了。

因此一个异步程序只有在没有任务可执行时才会出现"阻塞",这也是为什么异步程序被称为非阻塞程序的原因。
一个网络服务是异步模型的典型代表

2.异步编程模式与Reactor初探
先实现了一个阻塞的服务器blocking-server/slowpoetry.py和一个阻塞的客户端blocking-client/get-poetry.py
自然效率很低
然后使用一个异步客户端async-client/get-poetry.py,提升了效率。这个客户端并没有用到twisted
核心代码:

            while True:
                try:
                    new_data = sock.recv(1024)
                except socket.error, e:
                    if e.args[0] == errno.EWOULDBLOCK:
                        # this error code means we would have
                        # blocked if the socket was blocking.
                        # instead we skip to the next socket
                        break
                    raise
                else:
                    if not new_data:
                        break
                    else:
                        data += new_data

1用来进行通信的Socket方法是非阻塞模的,这是通过调用setblocking(0)来实现的。
2select模块中的select方法是用来识别其监视的socket是否有完成数据接收的,如果没有它就处于阻塞状态。
3当从服务器中读取数据时,会尽量多地从Socket读取数据直到它阻塞为止,然后读下一个Socket接收的数据(如果有数据接收的话)。

我们的异步模式的客户端必须要有一个循环体来保证我们能够同时监视所有的socket端。这样我们就能在一次循环体中处理尽可能多的数据。
这个利用循环体来等待事件发生,然后处理发生的事件的模型非常常见,而被设计成为一个模式:reactor模式。

说起这个圈,其实我想到让服务生打电话叫自己起床这么个有名的事件驱动的比喻。事实上,服务生一直在不断地检查:是否天亮了,如果天亮了就打电话,没有天亮就继续检查是否天亮。并不是轮询消失了,而是轮询由我变成了另一个人(服务生),所以事件驱动有个别名叫事件轮询。但是,要注意在twisted里,只有一个主线程,不管是轮询还是业务逻辑,都只有一个线程。
在整个epoll的逻辑里,不断轮询的也不是epoll函数本身,而是更底层的CPU中断引脚,CPU每执行完一次指令,都会检测中断引脚上是否有信号(“轮询”)。当有网络数据到来时,才会通知(调用)上层回调函数,上层接着回调下去直到我们的处理程序。

一个真正reactor模式的实现是需要实现循环独立抽象出来并具有如下的功能:
1监视一系列与你I/O操作相关的文件描述符(description)
2不停地向你汇报那些准备好的I/O操作的文件描述符

感觉就像是一个老板的秘书,报告重要事件。

3.初识Twisted
用twisted的方式实现前面的内容

from twisted.internet import reactor
reactor.run()

正常情况下,我们需要给出事件循环或者文件描述符来监视I/O(连接到某个服务器上,比如说我们那个诗歌服务器)。
值得注意的是,这里并不是一个在不停运行的简单循环。如果你在桌面上有个CPU性能查看器,可以发现这个循环体不会带来任何性能损失。实际上,这个reactor被卡住在第二部分图5的最顶端,等待永远不会到来的事件发生(更具体点说是一个调用select函数,却没有监视任何文件描述符)。
注意,此时进程是属于阻塞态的(linux下进程状态显示为S),不消耗任何系统资源。
为了避免CPU空转,可以引进了一个代理(一开始有一位叫做select的代理,后来又有一位叫做poll的代理,不过两者的本质是一样的)。这个代理比较厉害,可以同时观察许多流的I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中醒来,于是我们的程序就会轮询一遍所有的流(于是我们可以把“忙”字去掉了)。
[外链图片转存失败(img-FSVY8vQ2-1562752231844)(http://wiki.jikexueyuan.com/project/twisted-intro/images/p02_reactor-1.png)]
这一段还需要深入理解。为什么他被“卡”在最顶端,是真的“卡住了”?
2019-6-29:没错,是真的被卡住了,因为当fd集合为空,select检查完毕后,就会挂起自己,把cpu让给其他线程使用。

reactor是单例模式,只能存在一个,并且只要import就会创建
若使用其它的reactor,需要在引入twisted.internet.reactor前安装它。

from twited.internet import pollreactor
pollreactor.install()
from twisted.internet import reactor
reactor.run()

通过调用reactor的callWhenRunning函数,并传给它一个我们想调用函数的引用来实现hello函数的调用。
由于Twisted循环是独立于我们的代码,我们的业务代码与reactor核心代码的绝大多数交互都是通过使用Twisted的APIs回调我们的业务函数来实现的。
reactor和我们的业务函数是在一个线程里的,我之前认为轮询是由另一个线程来完成(可能其他事件驱动的程序可能是这样的,比如服务生打电话,但reactor不是。)

很多标准的Python方法没有办法转换为非阻塞方式。例如,os.system中的很多方法会在子进程完成前一直处于阻塞状态,这也就是它工作的方式。所以当你使用Twisted时,避开使用os.system。
避免在自己的函数里写会阻塞的代码

reactor.stop() # 退出
reactor.callLater(1, self.count)

注册一个1秒后运行的函数,这个方法和之前Tk的方法名字都一样啊
你可以将超时作为图5中循环等待中的一种事件来看待。
Twisted的callLater机制并不为硬实时系统提供任何时间上的保证。

捕获它,Twisted
reactor并不会因为回调函数中出现失败(虽然它会报告异常)而停止运行。而是会继续执行下一个函数

4.由twisted支持的客户端
前面一节讲解了twisted的一些最基础语法。这一节开始分析两个twisted程序,一个是服务器,一个是客户端
第一个twisted支持的诗歌服务器
先讲解客户端:
twisted-client-1/get-poetry.py
这个文件有一个类PoetrySocket,其中有fileno,connectLost,doRead等方法,这些都是内置方法。当触发了对应的事件的时候reactor会自动调用他们。
addReader将自己传递给 reactor,给reactor指定监控的文件描述符。
doRead方法。当其被Twisted的reactor调用时,就会采用异步的方式从socket中读取数据。(IReadDescriptor)
fileno返回我们想监听的文件描述符,connectionLost是当连接关闭时被调用。(IFileDescriptor)
logPrefix(ILoggingContext)

doRead等其实就是一个回调函数,只是没有直接将其传递给reactor,而是传递一个实现此方法的对象实例。这也是Twisted框架中的惯例—不是直接传递实现某个接口的函数而是传递实现它的对象。这样我们通过一个参数就可以传递一组相关的回调函数。而且也可以让回调函数之间通过存储在对象中的数据进行通信。

twisted-client-1/get-poetry-broken.py 没有选择非阻塞的写法。效率和一开始的客户端没有区别

应该禁止使用其它各类的阻塞函数,如os.system中的函数。除此之外,当我们遇到计算型的任务(长时间占用CPU),最好是将任务切成若干个部分执行以让I/O操作尽可能地执行。

其他:两个低层reactor的APIs:removeReader和getReaders。

5.由Twisted扶持的客户端
三个新概念:
Transports, Protocols, Protocol Factories
一个Twisted的Transport代表一个可以收发字节的单条连接。对于我们的诗歌下载客户端而言,就是对一条TCP连接的抽象。但是Twisted也支持诸如Unix中管道和UDP。
Protocol实例是存储协议状态与间断性(由于我们是通过异步I/O方式以任意大小来接收数据的)接收并累积数据的地方。
factory是用来给不同protocol之间交换信息,一个protocol可以有不同factory
reactor run之前创建过程,Factory生成 protocol
reactor run之前
在Protocol创立的第二步便是通过makeConnection与一个Transport联系起来。
[外链图片转存失败(img-jyBvnr0u-1562752231845)(http://wiki.jikexueyuan.com/project/twisted-intro/images/p05_protocols-2.png)]

处理接收到数据的主要方法是dataReceived

def dataReceived(self, data):
    self.poem += data

twisted-client-2/get-poetry.py 实现了上面的一切
twisted-client-2/get-poetry-stack.py 打印一个运行时的跟踪堆栈
IReadDescriptor实际上就是变相的Transport类

当transport的连接关闭时,conncetionLost回调会被激活。reason参数是一个twisted.python.failure.Failure的实例对象,其携带的信息能够说明连接是被安全的关闭还是由于出错被关闭的。

6.更加"抽象"的运用Twisted
客户端版本2仅仅比较初步地使用了Protocol和Factory,3优化了设计
使Factory只做好两件事,1、生成一个PoetryProtocol的实例 2、收集下载完毕的诗歌的工作
由get_poetry负责创建工厂和connectTCP
Factory用一个回调来将下载完毕的诗歌传回去。
这样子更好地复用这三个类,而开启和关闭reactor都在main函数中完成。

开始讨论异常问题
在一个异步交互的程序中,错误信息也必须异步的传递出去(以保证reactor被正确关闭,如果不处理异常,reactor就永远不会被关闭,在空转。)。
1、使用Exception
2、使用一个异常跟踪栈
所以,我们需要写一个处理错误信息的回调函数

    def clientConnectionFailed(self, connector, reason):
        self.errback(reason)

7.小插曲 Deferred
用defer来解决异步代码中的异常处理问题:
client3-1中的poem_failed错误回调是由我们自己的代码激活并调用的,即PoetryClientFactory的clientConnectFailed函数。是我们自己而不是Python来确保当出错时错误处理代码能够执行。因此我们必须保证通过调用携带Failure对象的errback来处理任何可能的错误。
否则,reactor就关不了。

在异步程序中处理错误信息比处理正常的信息要重要的多,这是因为错误会以多种方式出现,而正确的结果出现的方式是唯一的。clientConnectionFailed是个特定类型的错误处理,如果在回调函数中出现了其他的不可预料的错误,也需要进行正确的处理,终止reactor。

一个特性:
deferred不允许别人激活它两次
先使用d = defer.Deferred() 创建一个回调链,再使用addcallback和addboth往里面塞东西,最后
reactor.callWhenRunning(d.callback, ‘Another short poem.’)
可以调用reactor的callWhenRunning函数来激活deferred。再给callWhenRunning函数一个额外的参数传给回调函数。

小实验:
在defer-9.py中加入一行

def poem_done(_):  # done是回调链最后一环
    a = 1/0
    from twisted.internet import reactor
    reactor.stop()

依然会导致reactor无法结束的错误。

如果

def got_poem(poem):  # 接着执行done
    print(poem)
    a=1/0

系统不会报错但会正常结束。接着在done里面加入

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

系统正常结束,且会报错误
应该保证最后addBoth的代码简单,这样不会出错,正确关闭reactor。

8.使用Deferred的诗歌下载客户端
实现一个使用defer的客户端4.0:
之前3.0版本是直接使用回调,这样如果的话,出错就会GG,所以要使用deferred的d.callback调用回调
所以要先把d传入工厂,接着在poem_finish回调之后在工厂里调用callback

d, self.deferred = self.deferred, None  # 销毁其引用。这样做可以保证我们不会激活一个deferred两次。

讨论:
同步函数也能返回一个deferred,因此严格来说,返回deferred只能说可能是异步的。我们会在将来的例子中会看到同步函数返回deferred。

9.第二个小插曲,deferred
在3.1的基础上进行讲解,如果没有正确捕获异常会发生什么:
这个异常会传播到工厂中的poem_finished回调,即激活got_poem的方法
由于poem_finished并没有捕获这个异常,因此其会传递到protocol中的poemReceive函数
然后来到connectionLost函数,仍然在protocol中
然后就来到Twisted的核心区,最后止步于reactor。

在got_poem后面任何一步都没有可能以符合我们客户端的具体要求来处理异常的机会。
这段内容详细介绍了defered和异常处理。
在这里插入图片描述
在正常顺序调用时,最底层的是connect函数,get_poem的调用顺序显然应该在connect之前。
在回调情况下,最底层的是got_poem函数,它发生了错误之后,反而会传递到connect,所以:
由于bug可能存在于我们代码中的每个角落,因此我们必须将每个回调都放入try/except中,这样一来所有的异常都才有可能被捕获。这对于我们的errback同样适用,因为errback中也可能含有bugs。
回调链的执行顺序
上图为回调链一个可能的的执行顺序

未处理的defer异常(最后一步出现错误):
最后一个print函数成功执行,意味着程序并没有因为出现未处理异常而崩溃。
其只是将跟踪栈打印出来,而没有宕掉解释器
跟踪栈的内容告诉我们deferred在何处捕获了异常
"Unhandle"的字符在"Finished"之后出现。

之所以出现第4条是因为,这个消息只有在deferred被垃圾回收时才会打印出来。

Callbacks与Errbacks,总会成对出现
addCallbacks和addBoth 向链中添加函数对
addCallback和addErrback 也向链中添加函数对,addCallback向链中添加一个显式的callback函数与一个隐式的"pass-through"函数(实在想不出一个对应的词)。一个pass-through函数只是虚设的函数,只将其第一个参数返回。由于errback回调函数的第一个参数是Failure,因此一个"path-through"的errback总是执行"失败",即将异常传给下个errback回调。

deferred模拟器
这部分内容主要是帮助理解deferred,但你会发现,读其中的代码twisted-deferred/deferred-simulator.py ,可以更好的理解deferred。

10.增强defer功能的客户端
增强defer客户端5.0
在获得诗歌之后进行新的处理逻辑
如果上游的链返回了一个None,控权会进入到下一级的callback链中。

5.1版本改写:
之前在5.0版本中try_to_cummingsify函数依然使用了try/except语句,可以将其改写成使用defered的形式。
让deferred来捕获 GibberishError 与ValueError 异常在这里插入图片描述

总结:控制权在deferred的回调链中交错传递具体方向依赖于返回值的类型。

11、12章和deferred没太大关系,主要是twisted的常用协议介绍
11.改进诗歌下载服务器(一)
之前的诗歌服务器时使用同步写法的,现在用Twisted框架来改写:
服务器端使用Protocol来管理连接(transport)
实现源码:twisted-server-1/fastpoetry.py
Protocol是从Factory中获得诗歌内容的:
除了创建PoetryProtocol, 工厂仅有的工作是存储要发送的诗歌。

*connectionMade都会在Protocol初始化时被调用,只是我们在客户端处没有使用这个方法。
并且我们在客户端的portocal中实现的方法也没有在服务器中用到。因此,如果我们有这个需要,可以创建一个共享的PoetryProtocol供客户端与服务器端同时使用。*这样可以实现向服务器推送数据。
每一个客户端连接都有一个transport,代表一个client socket,加上listening socket总共是四个被select循环监听的文件描述符(file descriptor).

12.改进诗歌下载服务器(二)
除了诗歌下载之外,实现一个诗歌样式转换服务器
样式转换服务需要两者进行双向交互-客户端将原始式样的诗歌发送给服务器,然后服务器转换格式并将其返回给对应的客户端。因此,我们需要使用或自己实现一个协议来实现这种交互。
客户端需要向服务器端发送两部分信息:转换方式与诗歌原始内容。服务器只是将转换格式之后的诗歌发送给客户端。这里使用到了简单的运程调用。
远程过程调用:
Twisted支持若干种能解决这个问题的协议:XML-RPC, Perspective Broker, AMP。
什么是netstring
twisted-server-1/transformedpoetry.py
格式转换服务与具体协议的实现是完全分离的。将协议逻辑与服务逻辑分开是Twisted编程中常见的模式。这样做可以通过多种协议实现同一种服务,以增加代码的重用性。

    def transform(self, xform_name, poem):
        thunk = getattr(self, 'xform_%s' % (xform_name,), None)

通过xfomr_前缀式方法来获取服务方法。
考虑到客户端可以发送任意的transform方法名,这是一种防止客户端蓄意使用恶性代码来让服务器端执行的方法。这种方法也提供了实现由服务提供具体协议代理的机制。
NetStringProtocol实现不错

客户端实现
twisted-server-1/transform-test

总结:
双向通信
基于Twisted已有的协议实现新协议
将协议实现与服务功能实现独立分开

13.使用Deferred新功能实现新客户端
twisted-deferred/defer-10.py
展示了deferred最复杂的功能,一个外层defer,一个内层defer,一开始外层先执行,当外层defer执行到内层时,暂停,当内层被激活时开始继续执行执行内层完毕,然后转向外层。
在这里插入图片描述

客户端版本6.0
使用新学的deferred嵌套来重写我们的客户端来使用由服务器提供的样式转换服务。
这里之所以要使用双重嵌套是因为我们要实现两个服务,第一步,从两个服务器下载诗歌,随后将这两首诗歌传给诗歌转换服务器并得到最终结果,我们把诗歌传给转换服务器时,reactor会运行,此时在进行等待转换后的诗歌到来(这一步如果作为一个单独的任务拆分出来是需要自己的deferred的。)。
两次等待结果,两个deferred

14.Deferred用于同步环境
twisted-deferred/defer-11.py
第一组例子:
deferred可以在返回之前就激活。可以在一个已经激活的deferred上添加回调处理函数。
这里的话每当我们后续添加一个回调函数对都会立即执行这个回调函数。

    def addCallbacks(self, callback, errback=None,
                     callbackArgs=None, callbackKeywords=None,
                     errbackArgs=None, errbackKeywords=None):
		#省略添加代码
        if self.called:  #如果deferred已被激活
            self._runCallbacks()
        return self

第二组例子:
演示了deferred中的pause与unpause函数的功能,
pause:暂停一个已经激活的deferred。
unpause:解除暂停。这个机制类似于“当Deferred回调链上的回调函数又返回Deferred时,Deferred暂停自己”。

    def _runCallbacks(self):
    	。。。
        while chain:
            current = chain[-1]

            if current.paused:
                return

如果使用了pause,当添加完所有的callback对后unpause一次性执行

d = Deferred()
print('Pausing, then firing deferred.')
d.pause()
d.callback(0)

print('Adding callbacks.')
d.addCallback(callback_1)
d.addCallback(callback_2)
d.addCallback(callback_3)

print('Unpausing the deferred.')
d.unpause()

代理 1.0版本
一个有缓存的代理服务器版本:
该服务器既作为服务器向客户端请求提供本地缓存的诗歌,同时也要作为向外部诗歌下载服务器提出下载请求的客户端,因此其有两套协议/工厂,一套实现服务器角色,另一套实现客户端角色。
考虑到客户端端发送请求来时,缓存代理可能会将本地缓冲的诗歌取出返回,也有可能需要异步等待外部诗歌下载服务器的回复。如此一来,就会出现这样的情景:客户端发送来的请求,缓存代理处理请求可能是同步也可能是异步。
也就是说,返回值可能是deferred对象或者是一个确定值。maybeDeferred函数解决了这个问题。如果返回值不是defer,它会把返回值打包为一个已经激活的defer传入回调链中。
要解决这个需要,就用到了之前的特性:可以在返回Deferred前就激活。
两套协议/工厂,一套实现服务器角色,另一套实现客户端角色。

d = maybeDeferred(self.factory.service.get_poem)

或者直接用succeed手动打包,maybeDeferred底层也是用succeed包装的。

return succeed(self.poem)  # 这里直接返回一个deferred

Deferred可以在激活后添加新的回调也间接说明了我们在第九部分twisted-deferred/defer-unhandled.py提到的,deferred中会在最后一个回调中遇到未处理异常,并在此deferred被垃圾回收(即其已经没有任何外界引用)时才将该异常的情况打印出来。即deferred会在其销毁前一直持有异常,等待可能还会添加进来的回调来处理。

15、16测试和进程守护跳过,考虑到这两章对理解deferred并没有帮助。

17.构造"回调"的另一种方法
generators 是创建回调的候选方法.
以生成器本身的角度看问题:

生成器函数在被循环调用之前并没有执行(使用 next 方法)
一旦生成器开始运行,它将一直执行直到返回"循环"(使用 yield)
生成器与循环总是交错进行,一个运行,一个就不运行:
当循环中运行其他代码时(如 print 语句),生成器则没有运行
当生成器运行时, 则循环没有运行(等待生成器返回前它被"阻滞"了)
一旦生成器将控制交还到循环,再启动可能需要等待任意时间(其间任意量的代码可能被执行)

这与异步系统中的回调工作方式非常类似.
1、把 while 循环视作 reactor
2、把生成器视作一系列由 yield 语句分隔的回调函数.
3、通过send可以得到返回值res
注意( 不同之处在于,所有的回调分享相同的局部变量名空间, 而且名空间在不同回调中保持一致.)

一次激活多个生成器:
twisted-intro/inline-callbacks/gen-3.py

对比defer,如果我们需要使用内外嵌套的的defer的时候, 可以用生成器yield一个deferred的方式来解决。
在等被抛出的(内层deferred)执行完毕后将结果传回生成器内继续执行。
参见inline-callbacks/inline-callbacks-1.py的代码

inline-callbacks的内部实现流程:
在这里插入图片描述
代码简写:

def _inlineCallbacks(result, g, deferred):
	while(1):
		try:
			isFailure = isinstance(result, failure.Failure)
			#先判断是否失败
			result = g.send(result)
		except StopIteration:
			return xxx
		if isinstance(result, Deferred): #处理嵌套deferred情况
			#sth to do
		return deferred
		#由于不能确切地知道生成器何时停止(它可能生成一个或多个 deferred),装饰函数本身是异步的,所以 deferred 是一个合适的返回值.

1.inlineCallbacks将一个生成器按照上述策略转化为一系列异步回调.
2.inlineCallbacks 会帮我们调用 send 或 throw 方法,处理细节,并确保生成器运行到结束(假设它不抛出异常).
3.如果我们从生成器yield一个非deferred值,它将以 yield 生成的值立即重启.
4.如果我们从生成器yield一个 deferred,它不会重启。除非此 deferred 运行结束.如果 deferred 成功返回,则 yield 的结果就是 deferred 的结果.如果 deferred 失败了,则 yield 会抛出异常. 注意这个异常仅仅是一个普通的 Exception 对象,而不是 Failure,我们可以在 yield 外面用 try/except 块捕获它们.

通常嵌套过程中我们会生成一个由其他的异步操作(如 get_poetry,隔网络下载诗歌的过程)返回的deferred)

阅读inline-callbacks/inline-callbacks-2.py,最终装饰过的函数通过returnValue返回值或者一个异常返回。
如果生成器抛出一个异常,那么返回的 deferred 将激发它的错误回调链,把异常包含在一个 Failure 中. 但是如果我们希望生成器返回一个正常值,必须使用 defer.returnValue 函数. 像普通 return 语句一样,它也会终止生成器(实际会抛出一个特殊异常).例子 inline-callbacks/inline-callbacks-2.py 说明了这两种可能.

客户端7.0:

由于 inlineCallbacks 和 deferred 解决许多相同的问题,在它们之间如何选择呢?下面列出一些 inlineCallbacks 的潜在优势.
1.由于回调共享同一个命名空间,因此没有必要传递额外状态.
2.回调的顺序很容易看到,因为它总是从上到下执行.
3.节省了每个回调函数的声明和隐式控制流,通常可以减少输入工作量.
4.可以使用熟悉的 try/except 语句处理错误.

当然也存在一些缺陷:
生成器中的回调不能被单独调用,这使代码重用比较困难.而构造 deferred 的代码则能够以任意顺序自由地添加任何回调.
生成器的紧致性可能混淆一个事实,其实异步回调非常晦涩.尽管生成器看起来像一个普通的函数序列,但是它的行为却非常不一样. inlineCallbacks 函数不是一种避免学习异步编程模型的方式.

就像任何技术,实践将积累出必要的经验,帮你做出明智选择.

12.7的一些看法
需要注意javascript也是单线程的事件驱动
defer和promise之间的关系,twisted的deferred就是js的promise实现
由于python的很多库都是同步的,在用twisted写程序的时候存在很多坑,比如读取mysql数据库这种操作。
事实上inlinecallback装饰器已经接近协程的写法了,scrapy中大部分的写法都是如此。

生成器的惰性特征:所谓惰性计算,是函数式编程语言的一种特性,简单来说就是对于一个值,只有当用到它的时候才去计算,这个思想来自代数运算(对于很多未知数的方程组,事实上可以消掉一些不用求的未知数)。惰性运算的缺点是运行速度缓慢(每次用到一个值,编译器都会循环往上问:你有没有被求出来。。)
所以,考虑生成器表达式和列表表达式的不同行为,使用生成器是用空间换时间的一种策略。进一步参考后文的 惰性不是迟缓: Twisted和Haskell

17.队列
因此所有诗歌客户端需要解决这样一个问题:怎样得知你启动的所有异步操作都已经完成?目前我们通过将结果汇总到一个列表(如客户端 7.0中的 结果 列表)并检查这个列表的长度来解决这个问题.除了收集成功的结果,我们还必须小心地对待失败,否则一个失败将使程序以为还有工作需要做而进入死循环.

正如你所料,Twisted包含一个抽象层可以用来解决这个问题,我们来看一看.
DeferredList 类使我们可以将一个 defered 对象列表视为一个 defered 对象.通过这种方法我们启动一族异步操作并且在它们全部完成后获得通知(无论它们成功或者失败).
在以上例子中,回调被添加时立即激发,所以 DeferredList 也必须立即激发.我们一会儿再讨论.
deferred 列表的结果本身也是一个列表(空).
DeferredList 本身并不激发直到所有的原始列表中的 deferreds 都被激发. 而且以一个空列表创建的 DeferredList 会立即激发,因为它不需要等待任何 deferreds.
输出列表中结果的顺序与原始 deferred 列表顺序相对应,而不是 deferred 碰巧被激发的顺序.这一点非常好,因为我们可以很容易地将每个结果与生成它的相应的操作联系在一起(如哪首诗来自哪个服务器).

We got: [(True, 'd1 result'), (True, 'd2 result')]
#(logic,res(deferred的最终返回值))

DeferredList 不会失败,因为无论每个 deferred 的返回结果是什么都会被集总到结果列表中

d = defer.DeferredList([d1, d2], consumeErrors=True)

没有consumeErrors选项如果d2出现了错误就会报错
DeferredList 需要知道它所监视的 deferred 何时激发. DeferredList 以通常的方式向每个 deferred 添加一个回调和错误回调. 默认地,这个回调(或错误)返回原始结果(或错误)在将它们放入最终结果列表之后.由于错误回调返回原始 failure 后将触发下一个错误回调, d2 在它被激发后仍然保持失败状态.

但是如果我们将 consumeErrors=True 传递给 DeferredList, 它将向每个 deferred 添加返回 None 的错误回调, 即"消耗"掉这个错误并且取消警告信息. 我们同样可以向 d2 添加自己的错误回调来处理错误,如 deferred-list/deferred-list-7.py.

18.取消之前的意图
从Twisted 10.1.0开始,高层代码可以反向发送信息 —— 它可以告诉底层代码它不再需要其结果了
在这里插入图片描述

d.cancel()

实质是引起一个错误,这个错误可以被捕获。就像其他 deferred 错误一样.
可以指定一个回调函数,用于取消之后的擦屁股工作
注意 canceller 在错误回调链激发前被调用.其实我们可以在取消回调中选择使用任何结果或错误自己激发 deferred (这样就会优先于 CancelledError 失败).这两种情况在 deferred-cancel/defer-cancel-6.py 和 deferred-cancel/defer-cancel-7.py中进行了说明.
“取消” deferred 仅仅是取消回调链的执行,但实际上并没有终止异步操作.为了得到一个真正可取消异步操作,必须在它被创建时添加一个 cancel 回调.
callLater 方法的 文档 它的返回值是另一个实现了 IDelayedCall 的对象,用 cancel 方法我们可以阻止延迟的调用被执行.

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

取消外部 deferred 并没有使外部 cancel 回调被激发. 相反,它取消了内部 deferred,所以内部 cancel 回调被激发了,之后外部错误回调收到 CancelledError (来自内部 deferred).

19.轮子内的轮子: Twisted和Erlang
介绍了一下erlang,有点难懂,相似之处在于轮子
在这里插入图片描述

20.惰性不是迟缓: Twisted和Haskell
在懒惰计算语言中,程序的文字并不过多的描述怎样计算需要计算的东西.具体实施计算的细节一般留给了编译器和运行时系统.

同时,需要进一步指出,作为惰性计算推进的运行时可能一次只计算表达式的一部分(惰性的)而不是全部.一般地,运行时只提供维持当前计算继续所需的最小计算量.

在这里插入图片描述
可以把异步I/O和 reactor 模式视为一种有限形式的惰性计算.异步I/O的格言是:“仅仅推进你所拥有的数据”.同时惰性计算的格言是:“仅仅推进你所需的数据”.进一步,一个惰性计算语言在任何地方都使用这个格言,并不仅仅是有限范围的I/O.

但关键点在于,对于惰性计算语言,做异步I/O小菜一碟. 编译器和运行时已经被设计为一点一点地处理数据结构,因而惰性地处理到来的I/O数据流是标准问题. 如此Haskell运行时,就像Erlang运行时,简单地集成异步I/O为套接字抽象的一部分. 我们以实现一个Haskell诗歌客户端来展示这个概念.
没有回调,没有传来传去的消息,仅仅是一个关于我们希望程序做什么的简洁地描述,而且很少需要告诉它应该怎样做.其余的事情都是由Haskell编译器和运行时处理的.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值