【问题标题】:Simple async example with tornado python龙卷风 python 的简单异步示例
【发布时间】:2014-04-07 19:05:12
【问题描述】:

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

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

我需要在不阻塞的情况下在单独的进程中运行它。有可能吗?

【问题讨论】:

    标签: python asynchronous tornado


    【解决方案1】:

    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。该方法正在创建一个异步任务,该任务正在调用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 可能会有帮助。

    【讨论】:

    • 为什么不对 gen_async 函数使用异步等待?
    • @amrx 这个答案是在将 async/await 添加到 Python 之前编写的。
    【解决方案2】:
    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/A 后立即访问 http://127.0.0.1:5000/B 进行查看。

    【讨论】:

    • executor = ThreadPoolExecutor(max_workers=MAX_WORKERS) 如何连接到执行?
    【解决方案3】:

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

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

    @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 被推荐用于调用阻塞函数的用例。

    【讨论】:

      【解决方案4】:

      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 中,两者大多可以互换。

      值得一看:

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-12-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多