【问题标题】:Tornado web asynchronousTornado 网络异步
【发布时间】:2018-03-09 18:03:37
【问题描述】:

我正在尝试使用这段代码来理解@tornado.web.asynchronous。预期的代码应该处理异步 Web 请求,但它似乎没有按预期工作。有两个端点:

1) http://localhost:5000/A  (This is the time consuming request and 
takes a few seconds)
2) http://localhost:5000/B (This is the fast request and takes no time to return.

但是,当我点击浏览器转到 http://localhost:5000/A 然后在运行时转到 http://localhost:5000/B 第二个请求排队并仅在 A 完成后运行。 换句话说,一个任务很耗时,但它会阻止另一个更快的任务。我做错了什么?

import tornado.web
from tornado.ioloop import IOLoop

import sys, random, signal


class TestHandler(tornado.web.RequestHandler):

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


   def background_task(self):
      sm = 0
      for i in range(10 ** 8):
         sm = sm + 1
      return str(sm + random.randint(0, sm)) + "\n"

   @tornado.web.asynchronous
   def get(self):
      """ Request that asynchronously calls background task. """
      res = self.background_task()
      self.write(str(res))
      self.finish()


class TestHandler2(tornado.web.RequestHandler):

   @tornado.web.asynchronous
   def get(self):
      self.write('Response from server: ' + str(random.randint(0, 100000)) + "\n")
      self.finish()

   def sigterm_handler(signal, frame):
     # save the state here or do whatever you want
     print('SIGTERM: got kill, exiting')
     sys.exit(0)

   def main(argv):
     signal.signal(signal.SIGTERM, sigterm_handler)
     try:
       if argv:
         print ":argv:", argv
         application = tornado.web.Application([
        (r"/A", TestHandler),
        (r"/B", TestHandler2),
        ])

        application.listen(5000)
        IOLoop.instance().start()
      except KeyboardInterrupt:
        print "Caught interrupt"
     except Exception as e:
        print e.message
     finally:
        print "App: exited"

if __name__ == '__main__':
   sys.exit(main(sys.argv))

【问题讨论】:

  • 您在background_task 中执行的操作是CPU 绑定操作,因此它会阻塞服务器。考虑在另一个线程中运行它。我刚才在这里回答了一个类似的问题:stackoverflow.com/questions/47442700/…

标签: python asynchronous tornado


【解决方案1】:

根据documentation

为了最小化并发连接的成本,Tornado 使用 单线程事件循环。这意味着所有应用程序代码 应该以异步和非阻塞为目标,因为只有一个 操作可以一次激活。

要实现此目标,您需要正确准备RequestHandler。如果函数只执行同步操作,那么简单地将 @tornado.web.asynchronous 装饰器添加到任何函数(getpost 等)是不够的。

@tornado.web.asynchronous 装饰器有什么作用?

让我们看看get 函数。语句以同步方式一个接一个地执行。一旦工作完成并且函数返回请求被关闭。后台正在拨打self.finish() 的电话。但是,当我们使用 @tornado.web.asynchronous 装饰器时,请求不会在函数返回后关闭。因此用户必须调用self.finish() 才能完成HTTP 请求。如果没有这个装饰器,请求会在 get() 方法返回时自动完成。

查看此页面中的“示例 21” - tornado.web.asynchronous

@web.asynchronous
def get(self):
  http = httpclient.AsyncHTTPClient()
  http.fetch("http://example.com/", self._on_download)

def _on_download(self, response):
  self.finish()

get() 函数对http://example.com/ 页面执行异步调用。让我们假设这个调用是一个长动作。所以http.fetch() 函数被调用,稍后get() 函数返回(http.fetch() 仍在后台运行)。 Tornado 的IOLoop 可以在获取来自http://example.com/ 的数据时继续处理下一个请求。一旦 http.fetch() 函数调用完成,回调函数 - self._on_download - 就会被调用。然后self.finish()被调用,请求最终被关闭。这是用户可以在浏览器中看到结果的时刻。

这可能是由于httpclient.AsyncHTTPClient()。如果您使用httpclient.HTTPClient() 的同步版本,您将需要等待对http://example.com/ 的调用完成。然后get()函数将返回并处理下一个请求。

总而言之,如果您在 RequestHandler 中使用异步代码,则建议使用 @tornado.web.asynchronous 装饰器。否则对性能影响不大。

编辑:要解决您的问题,您可以在单独的线程中运行耗时的函数。这是您的TestHandler 类的一个简单示例:

class TestHandler(tornado.web.RequestHandler):
    def on_finish(self, response):
        self.write(response)
        self.finish()

    def async_function(base_function):
        @functools.wraps(base_function)
        def run_in_a_thread(*args, **kwargs):
            func_t = threading.Thread(target=base_function, args=args, kwargs=kwargs)
            func_t.start()
        return run_in_a_thread

    @async_function
    def background_task(self, callback):
        sm = 0
        for i in range(10 ** 8):
            sm = sm + 1
        callback(str(sm + random.randint(0, sm)))

    @tornado.web.asynchronous
    def get(self):
        res = self.background_task(self.on_finish)

您还需要将这些导入添加到您的代码中:

import threading
import functools
import threading

async_function 是一个装饰器函数。如果您不熟悉我建议阅读的主题(例如:decorators)并自行尝试。一般来说,我们的装饰器允许函数立即返回(因此主程序可以继续执行)并且处理同时在单独的线程中进行。一旦线程中的函数完成,我们调用一个回调函数,它将结果写给最终用户并关闭连接。

【讨论】:

  • 如何通过回调异步调用 background_task()?我不能像那个例子那样使用 fetch()。
猜你喜欢
  • 2011-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多