Twisted异步编程--Deferred

Twisted异步编程--Deferred

Twisted异步编程

这篇文档介绍了异步编程模型,以及在Twisted中抽象出的Deferred——象征着“承诺了必定会有的”结果,而且能够把最终结果传递给处理函数(Python中实现了__call__()方法的对象均可以称之为“函数”,方法也是以函数的形式存在的,所以将全部“function”译做“函数”。——译者注)。html

这篇文档适合于刚接触Twisted的读者,而且熟悉Python编程语言,至少从概念上熟悉网络的核心概念,诸如服务器、客户端和套接口。这篇文档将给你一个对并发编程的上层概观(交叉执行许多任务),以及Twisted的并发模型:非阻塞编码或者叫异步编码python

在讨论过包含有Deferred的并发模型以后,将介绍当函数返回了一个Deferred对象时,处理结果的方法。react

并发编程介绍

要完成某些计算任务常常须要很多时间,其缘由有两点:web

  1. 任务是计算集中型的(好比,求一个很大整数的全部因数),而且须要至关的CPU时间进行计算;或者
  2. 任务并非计算集中型的,可是须要等待某些数据,以产生结果。

等待回应

网络编程的基本功能就是等待数据。想象你有一个函数,这个函数会总结一些信息而且做为电子邮件发送。函数须要链接到一个远程服务器、等待服务器的回应、检查服务器可否处理这封电子邮件、等待回应、发送电子邮件、等待确认信息,而后断开链接。数据库

这其中任何一步都有可能占用很长时间。你的程序可能使用全部可能模型中最简单的一个——它实际上只是停下来等着数据的发送和接收,但在这种状况下,它有很是明显的基本限制:它不能同时发送多封电子邮件;而且在发送电子邮件的时候,它其实什么也作不了。编程

所以,除了最简单的以外,全部网络程序都会避免这种模型。另外还有许多不一样的模型,它们都容许你的程序在等待数据以继续某个任务的同时,继续作手头上的其余任务,这些模型你均可以采纳。api

不等待数据

编写网络程序有不少种办法,主要有这么几种:服务器

  1. 在不一样的操做系统进程中处理各个链接,这种状况下,操做系统会处理进程调度,好比当一个进程等待的时候,让其余进程继续工做;
  2. 在不一样的线程中处理各个链接1,这种状况下,线程框架会处理好诸如“当一个线程等待时,让其余线程继续工做”的问题;或者
  3. 在一个线程中,使用非阻塞的系统调用来处理全部链接。

非阻塞调用

上述第三种模型就是使用Twisted框架时的标准模型:非阻塞调用。网络

当在一个线程中处理多个链接时,调度就成为了应用程序的责任,而不是操做系统的。调度一般的实现方案是:当链接准备好读或是写时,调度系统会调用一个以前注册过的函数——一般被叫作异步事件驱动或是基于回调的编程。并发

在这种模型下,以前发送电子邮件的函数应该是象这样的:

  1. 调用一个链接函数,用以链接到远程服务器;
  2. 链接函数马上返回,暗示着当链接创建后,将调用电子邮件发送的通知;而且
  3. 链接一旦创建,系统就会通知发送电子邮件的函数,链接已经准备就绪。

于咱们最初阻塞的步骤相比,上面这种非阻塞的步骤有什么好处呢?当发送电子邮件的函数在链接创建以前没法继续的时候,程序的其余部分依然能够执行其余任务,好比说开始为其余电子邮件的链接执行相似的步骤。因而,整个程序就不会卡在等待一个链接的创建上。

callback

callback(回调)是通知应用程序数据已经就绪的经典模型。应用程序在调用一个方法,试图获取一些数据时,同时提供一个callback函数,当数据就绪时,这个 callback函数会被调用,而且就绪的数据就是调用的参数之一。所以,应用程序应该在callback函数中,继续执行以前获取这些数据时,想要执行的任务(当时没能立刻获得这些数据,因此当时没法执行这些任务——译者注)。

在同步编程中,一个函数会先请求数据,而后等待数据,最后处理它。在异步编程中,一个函数请求数据以后,会在数据准备就绪后,让外部库调用其callback函数。

Deferred

Twisted使用Deferred对象来管理callback序列。做为Twisted库的“客户端”,应用程序将一连串函数添加到Deferred对象中,当异步请求的结果准备就绪时,这一连串函数将被按顺序调用(这一连串函数被称为一个callback序列,或是一条callback链),一块儿添加的还有另一连串函数,当异步请求出现错误的时候,他们将被调用(称做一个errback序列,或是一条errback链)。异步库代码会在结果准备就绪时,调用第一个callback,或是在出现错误时,调用第一个errback,而后Deferred对象就会将callback或errback的返回结果传递给链中的下一个函数。

Deferred解决的问题

Deferred被设计用来帮助解决第二类并发问题——非计算集中型任务,而且延迟时间是能够估计的。等待硬盘访问,数据库访问和网络访问的函数都属于这一类,尽管时间延迟不尽相同。

Deferred被设计用来使Twisted程序能够无阻塞地等待数据,直到数据准备就绪。为了达到这个目的,Deferred为库与应用程序提供了一个简单的callback管理接口。库能够经过调用Deferred.callback来把准备就绪的数据传回给应用程序,或者调用Deferred.errback来报告一个错误。应用程序能够按它们但愿的顺序,设置结果处理逻辑,以callback与errback的形式添加到Deferred对象中去。

使 CPU尽量的有效率,是Deferred与该问题的其余解决方案背后的基本思路。若是一个任务在等待数据,与其让CPU(以及程序自己!)眼巴巴地等着数据(对于进程,这一般叫作“阻塞”),不如让程序同时执行些别的操做,而且同时留意着某些信号——一旦数据准备就绪,程序就能够(但不是当即——译者注)回到刚才的地方继续执行。

在Twisted里,一个函数返回一个Deferred对象,意味着它给调用者一个信号:它在等待数据。当数据准备就绪后,程序就会激活那个Deferred对象的callback链,来处理数据。

Deferred——数据即将到来的信号

在以前咱们发送电子邮件的例子中,父函数调用了一个链接远程服务器的函数。异步性要求这个链接函数必须不等待结果而直接返回,父函数才能够作其余事情。但是,父函数或者它的控制程序又是怎么知道链接还没有创建的呢?当链接创建后,它又是怎么使用链接的呢?

Twisted有一个对象能够做为这种状况的一个信号。链接函数返回一个twisted.internet.defer.Deferred对象,给出一个操做还没有完成的信号。

Deferred有两个目的。第一,它说“我是一个信号,只是通知你无论你刚才要我作的什么,结果尚未出来”。第二,你可让Deferred在结果出来后执行你的东西。

callback

添加一个callback——这就是你让Deferred在结果出来后执行你东西的办法,也就是让Deferred在结果出来后调用一个方法。

有一个Twisted库函数返回Deferred,就是twisted.web.client.getPage(这是一个异步获取网页的函数。——译者注)。在这个例子里,咱们调用了getPage——它返回了一个Deferred,而后添加了一个callback来处理返回的页面内容——固然处理是发生在数据准备就绪以后:

from twisted.web.client import getPage

from twisted.internet import reactor

def printContents(contents):
'''
这就是“callback”函数,被添加到Deferred,当“承诺了必定会有的数据”准备就绪后,
Deffered会调用它
'''

print "Deferred调用了printContents,内容以下:"
print contents

# 中止Twisted事件处理系统————这一般有更高层的办法
reactor.stop()

# 调用getPage,它会立刻返回一个Deferred————承诺一旦页面内容下载完了,
# 就会把他们传给咱们的callback们
deferred = getPage('http://twistedmatrix.com/') //一旦调用getPage下载页面完成,则当即调用callback对应的函数.即addCallback(printContents)
中的printContents函数.



# 给Deferred添加一个callback————要求它在页面内容下载完后,调用printContents
deferred.addCallback(printContents)

# 启动Twisted事件处理系统,一样,这不是一般的办法
reactor.run()

添加两个callback是一种很是常见的Deferred用法。第一个callback的返回结果会传给第二个callback:

from twisted.web.client import getPage

from twisted.internet import reactor

def lowerCaseContents(contents):
'''
这是一个“callback”函数,被添加到Deferred,当“承诺了必定会有的数据”准备就绪后,
Deffered会调用它。它把全部的数据变成小写
'''

return contents.lower()

def printContents(contents):
'''
这是一个“callback”函数,在lowerCaseContents以后被添加到Deferred,
Deferred会把lowerCaseContents的返回结果做为参数,调用这个callback
'''

print contents
reactor.stop()

deferred = getPage('http://twistedmatrix.com/')

# 向Deferred中添加两个callback————让Deferred在页面内容下载完以后执行
# lowerCaseContents,而后将其返回结果做为参数,调用printContents
deferred.addCallback(lowerCaseContents)     //当有多个addCallback的时候,第一个addCallback(lowerCaseContents)执行后的结果,会看成第二个addCallback(printContents)
函数的参数


deferred.addCallback(printContents)

reactor.run()

错误处理:errback

正如异步函数会在其结果产生以前返回,在有可能检测到错误以前返回也是能够的:失败的链接,错误的数据,协议错误,等等。正如你能够将callback添加到Deferred,你也能够将错误处理逻辑(“errback”)添加到Deferred,当出现错误,数据不能正常取回时,Deferred会调用它:

from twisted.web.client import getPage

from twisted.internet import reactor

def errorHandler(error):
'''
这是一个“errback”函数,被添加到Deferred,当出现错误事件是,Deferred将会调用它
'''

# 这么处理错误并非很实际,咱们只是把它打出来:
print "An error has occurred: <%s>" % str(error)
# 而后咱们中止整个处理过程:
reactor.stop()

def printContents(contents):
'''
这是一个“callback”函数,被添加到Deferred,Deferred会把页面内容做为参数调用它
'''

print contents
reactor.stop()

# 咱们请求一个不存在的页面,来演示错误链
deferred = getPage('http://twistedmatrix.com/does-not-exist')

# 向Deferred添加callback,以处理页面内容
deferred.addCallback(printContents)

# 向Deferred添加errback,以处理任何错误
deferred.addErrback(errorHandler)

reactor.run()

结论

在这篇文档中,你应该:

  1. 认识了为何复杂网络程序须要某种形式的并发性;
  2. 了解了Twisted框架用异步调用的形式支持并发;
  3. 了解了Twisted框架使用Deferred对象来管理callback链;
  4. 认识了getPage函数如何返回一个Deferred对象;
  5. 向那个Deferred对象添加了callback与errback;以及
  6. 认识了Deferred的callback链与errback链的触发。

参考资料

由于Deferred的抽象是Twisted编程中如此核心的一部分,以致于另外还有几篇关于它的详细的指南:

  1. 使用Deferred,一篇关于使用Deferred的更完整的指南,包括链式Deferred。
  2. 产生Deferred,一篇关于建立Deferred和触发他们callback链的指南。

脚注

  1. 这种方法有不少变种,好比说在一个有限大小的线程池中处理全部链接,本质上来讲,这是同一个想法的优化版。
相关文章
相关标签/搜索