前言

Tornado是一个高性能的http服务器实现,本文主要讲述tornado中协程的用法。

存在的问题

由于tornado是一个异步网络服务,所以在以下两类情形中需要进行异步处理,以免阻塞整个进程。一、需要访问另外一个服务(IO密集型);二、需要进行cpu密集型的运算。 第一类情形的解决方法是使用一个torando原生的异步client。 第二类情形的解决方法是使用executor将任务提交到其它进程或线程处理。

协程代码的写法

Tornado实现了基于generator的协程模型,使用这种技术可以简洁优美地编写异步代码。 下面说明两种情形的用法:

一、使用tornado原生异步client

tornado自带一个非阻塞HTTP client,用法如下:

class GenAsyncHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        http_client = AsyncHTTPClient()
        response = yield http_client.fetch("http://example.com")
        do_something_with_response(response)
        self.render("template.html")

二、使用executor调度任务

class ExecutorAsyncHandler(RequestHandler):
    executor = concurrent.futures.ThreadPoolExecutor(5)
    @gen.coroutine
    def get(self):
        result = yield queryModuleCapacityServerZoneInfo(date)
        do_something_with_result(result)
        self.render("template.html")
    @concurrent.futures.run_on_executor
    def queryModuleCapacityServerZoneInfo(self, date):
        query = ModuleCapacityServerZoneInfo.select().where(ModuleCapacityServerZoneInfo.date==date).dicts()
        return list(query)

应用中需要注意的问题

一、使用函数封装对asyncHTTPClient的返回数据进行处理。

    @gen.coroutine
    def get_citys_async(self,all_dict=False):
        #获取所有城市信息
        result = yield client.cmdb_async_client.CMDBClient.api('GetCities', {
                'fields': ["id","name","en_name"],
                'sn': setting.settings["cmdb_sn"],
            })
        city_info_list = result['data']
        if all_dict:
            city_all_dict = {}
            for city_item in city_info_list:
                city_all_dict[city_item["id"]] = city_item
            raise gen.Return(city_all_dict)
        else:
            raise gen.Return(city_info_list)

在python3.3之前的版本需要抛出gen.Return异常来返回结果。

二、可以使用list或dict方式实现多个异步并发处理。

    @gen.coroutine
    def getModuleCapacityGeneralInfo(self):
        server_info_list, general_info_list, city_id_dict, module_id_dict, zone_id_dict = yield [
                    self.queryModuleCapacityServerZoneInfo(date), 
                    self.queryModuleCapacityGeneralInfo(start, limit, date),
                    self.get_citys_async(all_dict=True),
                    self.get_modules_async(all_dict=True), 
                    self.get_zones_async(all_dict=True)                    
                    ]

三、协程yield中的异常处理与平常相同。

协程执行中抛出的异常会保存在future中,在yield时再次raise,具体是在future的result方法中实现。

    try:
        res = yield throw()
    except Exception, e:
        print 'EXCEPTION!', e

引申阅读:

tornado协程(coroutine)原理

http://blog.csdn.net/wyx819/article/details/45420017

Python generators yield详解

http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python