文档章节

使用tornado的coroutine进行编程

mickelfeng
 mickelfeng
发布于 2017/02/28 18:03
字数 1605
阅读 5
收藏 0

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

 

class MaindHandler(web.RequestHandler):

   @asynchronous

   @gen.coroutine

   def post(self):

       client = AsyncHTTPClient()

       resp = yield client.fetch(https://api.github.com/users")

       if resp.code == 200:

           resp = escape.json_decode(resp.body)

           self.write(json.dumps(resp, indent=4, separators=(',', ':')))

       else:

           resp = {"message": "error when fetch something"}

           self.write(json.dumps(resp, indent=4, separators={',', ':')))

       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的。举一个例子:

def test_yield():

   pirnt "test yeild"

   says = (yield)

   print says

 

if __name__ == "__main__":

   client = test_yield()

   client.next()

   client.send("hello world")

输出结果如下:

 

test yeild

hello world

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

Tornado异步编程

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

@gen.coroutine

def post(self):

   client = AsyncHTTPClient()

   resp = yield client.fetch("https://api.github.com/users")

   if resp == 200:

       body = escape.json_decode(resy.body)

   else:

       body = {"message": "client fetch error"}

       logger.error("client fetch error %d, %s" % (resp.code, resp.message))

   self.write(escape.json_encode(body))

   self.finish()

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

 

@gen.coroutime

def post(self):

   resp = yield GetUser()

   self.write(resp)

 

@gen.coroutine

def GetUser():

   client = AsyncHTTPClient()

   resp = yield client.fetch("https://api.github.com/users")

   if resp.code == 200:

       resp = escape.json_decode(resp.body)

   else:

       resp = {"message": "fetch client error"}

       logger.error("client fetch error %d, %s" % (resp.code, resp.message))

   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接口去抓取数据。大自使用方法如下:

 

装饰器

 

 def sync_loop_call(delta=60 * 1000):

 """

 Wait for func down then process add_timeout

 """

     def wrap_loop(func):

         @wraps(func)

         @gen.coroutine

         def wrap_func(*args, **kwargs):

             options.logger.info("function %r start at %d" %

                                 (func.__name__, int(time.time())))

             try:

                 yield func(*args, **kwargs)

             except Exception, e:

                 options.logger.error("function %r error: %s" %

                                      (func.__name__, e))

             options.logger.info("function %r end at %d" %

                                 (func.__name__, int(time.time())))

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

                 datetime.timedelta(milliseconds=delta),

                 wrap_func)

         return wrap_func

     return wrap_loop

任务函数

 

 @sync_loop_call(delta=10 * 1000)

 def worker():

     """

     Do something

     """

添加任务

 

 if __name__ == "__main__":

 worker()

 app.listen(options.port)

 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被墙,需要翻墙才能访问。

 

总结

 

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

本文转载自:http://www.pythontab.com/html/2013/pythonweb_0621/457.html

共有 人打赏支持
mickelfeng

mickelfeng

粉丝 227
博文 2588
码字总数 562007
作品 0
成都
高级程序员
如何利用python tornado吃透web服务器

based on tornado 4.5.2 1999 年,Dan Kegel 向网络服务器提出了一个骇人听闻的难题:是时候让网络服务器去同时应对 10000 个客户端了,你觉得呢?毕竟网络已经变得很普及了。这就是著名的 C1...

Jun_Wong
2017/11/14
0
0
tornado异步非阻塞处理请求

【tornado模块】 主要模块 - FriendFeed 使用的基础 Web 框架,包含了 Tornado 的大多数重要的功能 - XHTML, JSON, URL 的编码/解码方法 - 对 的简单封装,使其更容易使用 - 基于 Python 的 ...

SibylY
2016/06/21
59
0
tornado常见的异步非堵塞写法

非堵塞和异步有什么区别? 非堵塞 在tornado的框架中非堵塞一般指得是网络I/O层面的socket数据接收模式(select或者epoll),不论用哪个模式,最终程序都会收到数据并处理数据(这个数据要么被...

极光火狐狸
2016/07/21
1K
2
tornado线程阻塞的解决

前言 也许有同学很迷惑:tornado不是标榜异步非阻塞解决10K问题的嘛?但是我却发现不是torando不好,而是你用错了. 比如最近发现一个事情:某网站打开页面很慢,服务器cpu/内存都正常.网络状态也...

单蛙
2016/06/17
272
0
在pika中使用tornado协程

pika中的例子写的很差,充满了回调,看的人头痛,既不符合python的开发原则,也不符合tornado的coroutine风格。于是做了一个非常简单的wrapper: 这样,你就可以直接了。感觉直观,简单了很多...

崔钢
2016/07/30
59
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

JS:异步 - 面试惨案

为什么会写这篇文章,很明显不符合我的性格的东西,原因是前段时间参与了一个面试,对于很多程序员来说,面试时候多么的鸦雀无声,事后心里就有多么的千军万马。去掉最开始毕业干了一年的Jav...

xmqywx
今天
0
0
Win10 64位系统,PHP 扩展 curl插件

执行:1. 拷贝php安装目录下,libeay32.dll、ssleay32.dll 、 libssh2.dll 到 C:\windows\system32 目录。2. 拷贝php/ext目录下, php_curl.dll 到 C:\windows\system32 目录; 3. p...

放飞E梦想O
今天
0
0
谈谈神秘的ES6——(五)解构赋值【对象篇】

上一节课我们了解了有关数组的解构赋值相关内容,这节课,我们接着,来讲讲对象的解构赋值。 解构不仅可以用于数组,还可以用于对象。 let { foo, bar } = { foo: "aaa", bar: "bbb" };fo...

JandenMa
今天
1
0
OSChina 周一乱弹 —— 有人要给本汪介绍妹子啦

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @莱布妮子 :分享水木年华的单曲《中学时代》@小小编辑 手机党少年们想听歌,请使劲儿戳(这里) @须臾时光:夏天还在做最后的挣扎,但是晚上...

小小编辑
今天
21
5
centos7安装redis及开机启动

配置编译环境: sudo yum install gcc-c++ 下载源码: wget http://download.redis.io/releases/redis-3.2.8.tar.gz 解压源码: tar -zxvf redis-3.2.8.tar.gz 进入到解压目录: cd redis-3......

hotsmile
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部