使用tornado的gen.coroutine进行异步编程

在tornado3发布之后,强化了coroutine的概念,在异步编程中,替代了原来的gen.engine, 变成现在的gen.coroutine。这个装饰器本来就是为了简化在tornado中的异步编程。避免写回调函数, 使得开发起来更加符合正常逻辑思维。

一个简单的例子如下:

  1. class MaindHandler(web.RequestHandler):
  2. @asynchronous
  3. @gen.coroutine
  4. def post(self):
  5. client = AsyncHTTPClient()
  6. resp = yield client.fetch(https://api.github.com/users")
  7. if resp.code == 200:
  8. resp = escape.json_decode(resp.body)
  9. self.write(json.dumps(resp, indent=4, separators=(‘,‘, ‘:‘)))
  10. else:
  11. resp = {"message": "error when fetch something"}
  12. self.write(json.dumps(resp, indent=4, separators={‘,‘, ‘:‘)))
  13. self.finish()

在yield语句之后,ioloop将会注册该事件,等到resp返回之后继续执行。这个过程是异步的。在这里使用json.dumps,而没有使用tornado自带的escape.json_encode,是因为在构建REST风格的API的时候,往往会从浏览器里访问获取JSON格式的数据。使用json.dumps格式化数据之后,在浏览器端显示查看的时候会更加友好。Github API就是这一风格的使用者。其实escape.json_encode就是对json.dumps的简单包装,我在提pull request要求包装更多功能的时候,作者的回答escape并不打算提供全部的json功能,使用者可以自己直接使用json模块。

Gen.coroutine原理

在之前一篇博客中讲到要使用tornado的异步特性,必须使用异步的库。否则单个进程阻塞,根本不会达到异步的效果。 Tornado的异步库中最常用的就是自带的AsyncHTTPClient,以及在其基础上实现的OpenID登录验证接口。另外更多的异步库可以在这里找到。包括用的比较多的MongoDB的Driver。

在3.0版本之后,gen.coroutine模块显得比较突出。coroutine装饰器可以让本来靠回调的异步编程看起来像同步编程。其中便是利用了Python中生成器的Send函数。在生成器中,yield关键字往往会与正常函数中的return相比。它可以被当成迭代器,从而使用next()返回yield的结果。但是生成器还有另外一个用法,就是使用send方法。在生成器内部可以将yield的结果赋值给一个变量,而这个值是通过外部的生成器client来send的。举一个例子:

  1. def test_yield():
  2. pirnt "test yeild"
  3. says = (yield)
  4. print says
  5. if __name__ == "__main__":
  6. client = test_yield()
  7. client.next()
  8. client.send("hello world")

输出结果如下:

test yeild

hello world

已经在运行的函数会挂起,直到调用它的client使用send方法,原来函数继续运行。而这里的gen.coroutine方法就是异步执行需要的操作,然后等待结果返回之后,再send到原函数,原函数则会继续执行,这样就以同步方式写的代码达到了异步执行的效果。

Tornado异步编程

使用coroutine实现函数分离的异步编程。具体如下:

  1. @gen.coroutine
  2. def post(self):
  3. client = AsyncHTTPClient()
  4. resp = yield client.fetch("https://api.github.com/users")
  5. if resp == 200:
  6. body = escape.json_decode(resy.body)
  7. else:
  8. body = {"message": "client fetch error"}
  9. logger.error("client fetch error %d, %s" % (resp.code, resp.message))
  10. self.write(escape.json_encode(body))
  11. self.finish()

换成函数之后可以变成这样;

  1. @gen.coroutime
  2. def post(self):
  3. resp = yield GetUser()
  4. self.write(resp)
  5. @gen.coroutine
  6. def GetUser():
  7. client = AsyncHTTPClient()
  8. resp = yield client.fetch("https://api.github.com/users")
  9. if resp.code == 200:
  10. resp = escape.json_decode(resp.body)
  11. else:
  12. resp = {"message": "fetch client error"}
  13. logger.error("client fetch error %d, %s" % (resp.code, resp.message))
  14. raise gen.Return(resp)

这里,当把异步封装在一个函数中的时候,并不是像普通程序那样使用return关键字进行返回,gen模块提供了一个gen.Return的方法。是通过raise方法实现的。这个也是和它是使用生成器方式实现有关的。

使用coroutine跑定时任务

Tornado中有这么一个方法:

tornado.ioloop.IOLoop.instance().add_timeout()

该方法是time.sleep的非阻塞版本,它接受一个时间长度和一个函数这两个参数。表示多少时间之后调用该函数。在这里它是基于ioloop的,因此是非阻塞的。该方法在客户端长连接以及回调函数编程中使用的比较多。但是用它来跑一些定时任务却是无奈之举。通常跑定时任务也没必要使用到它。但是我在使用heroku的时候,发现没有注册信用卡的话仅仅能够使用一个简单Web Application的托管。不能添加定时任务来跑。于是就想出这么一个方法。在这里,我主要使用它隔一段时间通过Github API接口去抓取数据。大自使用方法如下:

装饰器

  1. def sync_loop_call(delta=60 * 1000):
  2. """
  3. Wait for func down then process add_timeout
  4. """
  5. def wrap_loop(func):
  6. @wraps(func)
  7. @gen.coroutine
  8. def wrap_func(*args, **kwargs):
  9. options.logger.info("function %r start at %d" %
  10. (func.__name__, int(time.time())))
  11. try:
  12. yield func(*args, **kwargs)
  13. except Exception, e:
  14. options.logger.error("function %r error: %s" %
  15. (func.__name__, e))
  16. options.logger.info("function %r end at %d" %
  17. (func.__name__, int(time.time())))
  18. tornado.ioloop.IOLoop.instance().add_timeout(
  19. datetime.timedelta(milliseconds=delta),
  20. wrap_func)
  21. return wrap_func
  22. return wrap_loop

任务函数

  1. @sync_loop_call(delta=10 * 1000)
  2. def worker():
  3. """
  4. Do something
  5. """

添加任务

  1. if __name__ == "__main__":
  2. worker()
  3. app.listen(options.port)
  4. tornado.ioloop.IOLoop.instance().start()

这样做之后,当Web Application启动之后,定时任务就会随着跑起来,而且因为它是基于事件的,并且异步执行的,所以并不会影响Web服务的正常运行,当然任务不能是阻塞的或计算密集型的。我这里主要是抓取数据,而且用的是Tornado自带的异步抓取方法。

在sync_loop_call装饰器中,我在wrap_func函数上加了@gen.coroutine装饰器,这样就保证只有yeild的函数执行完之后,才会执行add_timeout操作。如果没有@gen.coroutine装饰器。那么不等到yeild返回,就会执行add_timeout了。

完整地例子可以参见我的Github,这个项目搭建在heroku上。用于展示Github用户活跃度排名和用户区域分布情况。可以访问Github-Data查看。由于国内heroku被墙,需要FQ才能访问。

总结

Tornado是一个非阻塞的web服务器以及web框架,但是在使用的时候只有使用异步的库才会真正发挥它异步的优势,当然有些时候因为App本身要求并不是很高,如果不是阻塞特别严重的话,也不会有问题。另外使用coroutine模块进行异步编程的时候,当把一个功能封装到一个函数中时,在函数运行中,即使出现错误,如果没有去捕捉的话也不会抛出,这在调试上显得非常困难。

时间: 2024-11-09 21:09:24

使用tornado的gen.coroutine进行异步编程的相关文章

Tornado源码分析系列之一: 化异步为'同步'的Future和gen.coroutine

转自:http://blog.nathon.wang/2015/06/24/tornado-source-insight-01-gen/ 用Tornado也有一段时间,Tornado的文档还是比较匮乏的,但是幸好其代码短小精悍,很有可读性,遇到问题时总是习惯深入到其源码中.这对于提升自己的Python水平和对于网络及HTTP的协议的理解也很有帮助.本文是Tornado源码系列的第一篇文章,网上关于Tornado源码分析的文章也不少,大多是从Event loop入手,分析Event loop的工作

tornado.gen.coroutine-编写异步函数

异步函数: 1. 返回Future 2. 必须有set_result( )或者set_exception( )调用. 这里展示一个异步socket读取的例子: 首先定义一个定时返回的服务器,来模拟耗时的操作 from tornado.tcpserver import TCPServer from tornado import ioloop from tornado import gen  from tornado.concurrent import Future def sleep(durati

Tornado 高并发源码分析之六---异步编程的几种实现方式

方式一:通过线程池或者进程池 导入库futures是python3自带的库,如果是python2,需要pip安装future这个库 备注:进程池和线程池写法相同 1 from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor 2 from tornado.concurrent import run_on_executor 3 4 def doing(s): 5 print('xiumian--{}'.format(

<史上最强>深入理解 Python 异步编程(上)

前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知道如何使用 Tornado.Twisted.Gevent 这类异步框架上,出现各种古怪的问题难以解决.而且使用了异步框架的部分同学,由于用法不对,感觉它并没牛逼到哪里去,所以很多同学做 Web 后端服务时还是采用 Flask.Django等传统的非异步框架. 从上两届 PyCon 技术大会看来,异步编程已经成了 Python 生态下一阶段的主旋律.如新兴的 Go.Rust.

深入理解 Python 异步编程(上)

http://python.jobbole.com/88291/ 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知道如何使用 Tornado.Twisted.Gevent 这类异步框架上,出现各种古怪的问题难以解决.而且使用了异步框架的部分同学,由于用法不对,感觉它并没牛逼到哪里去,所以很多同学做 Web 后端服务时还是采用 Flask.Django等传统的非异步框架. 从上两届 PyCon 技术大会看来,异步编程已经成

利用 Python yield 创建协程将异步编程同步化

在 Lua 和 Python 等脚本语言中,经常提到一个概念: 协程.也经常会有同学对协程的概念及其作用比较疑惑,本文今天就来探讨下协程的前世今生. 首先回答一个大家最关心的问题:协程的好处是什么? 通俗易懂的回答: 让原来要使用 异步 + 回调 方式写的非人类代码,可以用看似同步的方式写出来. 1.回顾同步与异步编程 同步编程即线性化编程,代码按照既定顺序执行,上一条语句执行完才会执行下一条,否则就一直等在那里. 但是许多实际操作都是CPU 密集型任务和 IO 密集型任务,比如网络请求,此时不

在tornado中使用celery实现异步任务处理之一

一.简介 tornado-celery是用于Tornado web框架的非阻塞 celery客户端. 通过tornado-celery可以将耗时任务加入到任务队列中处理, 在celery中创建任务,tornado中就可以像调用AsyncHttpClient一样调用这些任务. ? Celery中两个基本的概念:Broker.Backend Broker : 其实就是一开始说的 消息队列 ,用来发送和接受消息. Broker有几个方案可供选择:RabbitMQ,Redis,数据库等 Backend:

使用tornado让你的请求异步非阻塞

http://www.dongwm.com/archives/shi-yong-tornadorang-ni-de-qing-qiu-yi-bu-fei-zu-sai/?utm_source=tuicool&utm_medium=referral 前言 也许有同学很迷惑:tornado不是标榜异步非阻塞解决10K问题的嘛?但是我却发现不是torando不好,而是你用错了.比如最近发现一个事情:某网站打开页面很慢,服务器cpu/内存都正常.网络状态也良好. 后来发现,打开页面会有很多请求后端数据库

深入解析Javascript异步编程

这里深入探讨下Javascript的异步编程技术.(P.S. 本文较长,请准备好瓜子可乐 :D) 一. Javascript异步编程简介 至少在语言级别上,Javascript是单线程的,因此异步编程对其尤为重要. 拿nodejs来说,外壳是一层js语言,这是用户操作的层面,在这个层次上它是单线程运行的,也就是说我们不能像Java.Python这类语言在语言级别使用多线程能力.取而代之的是,nodejs编程中大量使用了异步编程技术,这是为了高效使用硬件,同时也可以不造成同步阻塞.不过nodejs