龙卷风python的简单异步示例

时间:2014-04-07 19:05:13

标签: python asynchronous tornado

我想找到简单的异步服务器示例。 我有很多等待,数据库事务等功能......等等:

def blocking_task(n):
    for i in xrange(n):
        print i
        sleep(1)
    return i

我需要在没有阻塞的情况下在分离的进程中运行它。有可能吗?

4 个答案:

答案 0 :(得分:16)

Tornado旨在在单个线程中运行所有操作,但利用异步I / O来尽可能避免阻塞。如果您正在使用的数据库具有异步Python绑定(理想情况下适用于Tornado,例如MongoDB的Motor或Postgres的momoko),那么您就可以运行数据库查询而不阻塞服务器;不需要单独的流程或线程。

为了解决您给出的确切示例,在调用time.sleep(1)的地方,您可以使用此方法通过龙卷风协同程序异步执行此操作:

#!/usr/bin/python

import tornado.web
from tornado.ioloop import IOLoop
from tornado import gen 
import time

@gen.coroutine
def async_sleep(seconds):
    yield gen.Task(IOLoop.instance().add_timeout, time.time() + seconds)

class TestHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        for i in xrange(100):
            print i
            yield async_sleep(1)
        self.write(str(i))
        self.finish()


application = tornado.web.Application([
    (r"/test", TestHandler),
    ])  

application.listen(9999)
IOLoop.instance().start()

有趣的部分是async_sleep。该方法正在创建一个异步Task,它调用ioloop.add_timeout方法。 add_timeout将在给定的秒数后运行指定的回调,而不会在等待超时到期时阻止ioloop。它需要两个论点:

add_timeout(deadline, callback) # deadline is the number of seconds to wait, callback is the method to call after deadline.

正如您在上面的示例中所看到的,我们只是在代码中明确地向add_timeout提供了一个参数,这意味着我们最终会这样:

add_timeout(time.time() + seconds, ???)

我们未提供预期的回调参数。实际上,当gen.Task执行add_timeout时,它会在显式提供的参数的末尾附加callback关键字参数。所以这个:

yield gen.Task(loop.add_timeout, time.time() + seconds)

结果在gen.Task()中执行:

loop.add_timeout(time.time() + seconds, callback=gen.Callback(some_unique_key))

在超时后执行gen.Callback时,它表示gen.Task已完成,程序执行将继续到下一行。这种流程很难完全理解,至少在开始时(当我第一次阅读它时,它肯定适合我)。阅读Tornado gen module documentation几次可能会有所帮助。

答案 1 :(得分:5)

import tornado.web
from tornado.ioloop import IOLoop
from tornado import gen

from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor   # `pip install futures` for python2

MAX_WORKERS = 16

class TestHandler(tornado.web.RequestHandler):
    executor = ThreadPoolExecutor(max_workers=MAX_WORKERS)

    """
    In below function goes your time consuming task
    """

    @run_on_executor
    def background_task(self):
        sm = 0
        for i in range(10 ** 8):
            sm = sm + 1

        return sm

    @tornado.gen.coroutine
    def get(self):
        """ Request that asynchronously calls background task. """
        res = yield self.background_task()
        self.write(str(res))

class TestHandler2(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        self.write('Response from server')
        self.finish()


application = tornado.web.Application([
    (r"/A", TestHandler),
    (r"/B", TestHandler2),
    ])

application.listen(5000)
IOLoop.instance().start()

当您运行上面的代码时,您可以在http://127.0.0.1:5000/A运行计算成本较高的操作,这不会阻止执行,请在访问http://127.0.0.1:5000/B后立即访问http://127.0.0.1:5000/A

答案 2 :(得分:3)

在这里,我更新了有关Tornado 5.0的信息。 Tornado 5.0添加了一种新方法IOLoop.run_in_executor。在Coroutine patterns章节的“调用阻止函数”中:

  

从协程调用阻塞函数的最简单方法是使用IOLoop.run_in_executor,它返回与协同程序兼容的Futures:

     

@gen.coroutine def call_blocking(): yield IOLoop.current().run_in_executor(blocking_func, args)

此外,在run_on_executor的文件中,说:

  

不应将此装饰器与名称相似的 IOLoop.run_in_executor 混淆。通常,建议在调用阻塞方法时使用run_in_executor,而不是在定义方法时使用此装饰器。如果需要与旧版本的Tornado兼容,请考虑在调用站点定义执行程序并使用executor.submit()。

在5.0版本中,在调用阻塞函数的用例中建议使用IOLoop.run_in_executor。

答案 3 :(得分:1)

Python 3.5 引入了 asyncawait 关键字(使用这些关键字的函数也称为“原生协程”)。为了与旧版本的 Python 兼容,您可以使用 tornado.gen.coroutine 装饰器使用“装饰”或“基于产量”的协程。

只要有可能,推荐使用原生协程。仅在需要与旧版本 Python 兼容时才使用修饰的协程。 Tornado 文档中的示例通常会使用原生格式。

这两种形式之间的翻译通常很简单:

# Decorated:                    # Native:

# Normal function declaration
# with decorator                # "async def" keywords
@gen.coroutine
def a():                        async def a():
    # "yield" all async funcs       # "await" all async funcs
    b = yield c()                   b = await c()
    # "return" and "yield"
    # cannot be mixed in
    # Python 2, so raise a
    # special exception.            # Return normally
    raise gen.Return(b)             return b

下面概述了两种形式的协程之间的其他差异。

  • 原生协程:

    • 通常更快。
    • 可以使用 async forasync with 语句使某些模式更加简单。
    • 除非您awaityield 它们,否则根本不要运行。装饰的协程可以在被调用后立即开始“在后台”运行。请注意,对于这两种协程,使用 awaityield 很重要,以便任何异常都可以处理。
  • 修饰的协程:

    • concurrent.futures 包有额外的集成,允许直接产生 executor.submit 的结果。对于原生协程,请改用 IOLoop.run_in_executor
    • 通过生成列表或字典来支持等待多个对象的一些简写。使用 tornado.gen.multi 在本机协程中执行此操作。
    • 可以通过转换函数注册表支持与其他包的集成,包括 Twisted。要在本机协程中访问此功能,请使用 tornado.gen.convert_yielded
    • 总是返回一个 Future 对象。本机协程返回一个不是 Future 的可等待对象。在 Tornado 中,两者大多可以互换。

值得一看: