简体   繁体   English

Tornado阻止异步请求

[英]Tornado blocking asynchronous requests

Using Tornado, I have a Get request that takes a long time as it makes many requests to another web service and processes the data, could take minutes to fully complete. 使用Tornado,我有一个Get请求需要很长时间,因为它向另一个Web服务发出许多请求并处理数据,可能需要几分钟才能完全完成。 I don't want this to block the entire web server from responding to other requests, which it currently does. 我不希望这阻止整个Web服务器响应其当前所做的其他请求。

As I understand it, Tornado is single threaded and executes each request synchronously, even though it handles them asynchronously (still confused on that bit). 据我了解,Tornado是单线程并同步执行每个请求,即使它异步处理它们(仍然在那个位置上混淆)。 There are parts of the long process that could be pause points to allow the server to handle other requests (possible solution?). 长进程的某些部分可能是暂停点,以允许服务器处理其他请求(可能的解决方案?)。 I'm running it on Heroku with a single worker, so not sure how that translates into spawning a new thread or multiprocessing, which I have no experience in with python. 我在Heroku上运行它只有一个工作者,所以不确定如何转换为产生一个新的线程或多处理,我没有使用python的经验。

Here is what I'm trying to do: the client makes the get call to start the process, then I loop through another get call every 5 seconds to check the status and update the page with new information (long polling would also work but running into the same issue). 这是我正在尝试做的事情:客户端进行get调用以启动进程,然后我每5秒循环一次另一个get调用以检查状态并使用新信息更新页面(长轮询也可以运行但运行进入同一问题)。 Problem is that starting the long process blocks all new get requests (or new long polling sessions) until it completes. 问题是启动长进程会阻止所有新的get请求(或新的长轮询会话),直到它完成为止。

Is there an easy way to kick off this long get call and not have it block the entire web server in the process? 是否有一种简单的方法可以启动这个长时间的呼叫,而不是让它在整个过程中阻止整个Web服务器? Is there anything I can put in the code to say.. "pause, go handle pending requests then continue on"? 有没有什么我可以在代码中说..“暂停,处理待处理的请求然后继续”?

I need to initiate a get request on ProcessHandler. 我需要在ProcessHandler上发起get请求。 I then need to continue to be able to query StatusHandler while ProcessHandler is running. 然后我需要继续能够在ProcessHandler运行时查询StatusHandler。

Example: 例:

class StatusHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
       self.render("status.html")

class ProcessHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
       self.updateStatus("0")
       result1 = self.function1()
       self.updateStatus("1")
       result2 = self.function2(result1)
       self.updateStatus("2")
       result3 = self.function3(result2)
       self.updateStatus("3")
       self.finish()

Here's a complete sample Tornado app that uses the Async HTTP client and the gen.Task module to make things simple. 这是一个完整的样本Tornado应用程序,它使用Async HTTP客户端和gen.Task模块来简化操作。

If you read more about gen.Task in the docs you'll see that you can actually dispatch multiple requests at the same time. 如果您在文档中阅读有关gen.Task更多信息,您会看到您实际上可以同时分派多个请求。 This is using the core idea of Tornado where everything is no blocking and still maintaining a single process. 这是使用Tornado的核心理念,其中一切都没有阻塞,仍然保持一个单一的过程。

Update: I've added a Thread handler to demonstrate how you could dispatch work into a second thread and receive the callback() when it's done. 更新:我添加了一个Thread处理程序来演示如何将工作分配到第二个线程并在完成后接收callback()

import os
import threading
import tornado.options
import tornado.ioloop
import tornado.httpserver
import tornado.httpclient
import tornado.web
from tornado import gen
from tornado.web import asynchronous

tornado.options.define('port', type=int, default=9000, help='server port number (default: 9000)')
tornado.options.define('debug', type=bool, default=False, help='run in debug mode with autoreload (default: False)')

class Worker(threading.Thread):
   def __init__(self, callback=None, *args, **kwargs):
        super(Worker, self).__init__(*args, **kwargs)
        self.callback = callback

   def run(self):
        import time
        time.sleep(10)
        self.callback('DONE')

class Application(tornado.web.Application):
    def __init__(self):
        handlers = [
            (r"/", IndexHandler),
            (r"/thread", ThreadHandler),
        ]
        settings = dict(
            static_path = os.path.join(os.path.dirname(__file__), "static"),
            template_path = os.path.join(os.path.dirname(__file__), "templates"),
            debug = tornado.options.options.debug,
        )
        tornado.web.Application.__init__(self, handlers, **settings)

class IndexHandler(tornado.web.RequestHandler):
    client = tornado.httpclient.AsyncHTTPClient()

    @asynchronous
    @gen.engine
    def get(self):
        response = yield gen.Task(self.client.fetch, "http://google.com")

        self.finish("Google's homepage is %d bytes long" % len(response.body))

class ThreadHandler(tornado.web.RequestHandler):
    @asynchronous
    def get(self):
        Worker(self.worker_done).start()

    def worker_done(self, value):
        self.finish(value)

def main():
    tornado.options.parse_command_line()
    http_server = tornado.httpserver.HTTPServer(Application())
    http_server.listen(tornado.options.options.port)
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()

koblas's solution is great. 科布拉斯的解决方案很棒。 Here is an alternative that makes use of tornado.gen 这是一个利用tornado.gen的替代方案

import tornado.ioloop
import tornado.web
import tornado.gen
import tornado.concurrent
import time
from threading import Thread
from functools import wraps

def run_async(func):
  @wraps(func)
  def async_func(*args, **kwargs):
    func_hl = Thread(target = func, args = args, kwargs = kwargs)
    func_hl.start()
    return func_hl

  return async_func

@run_async
def sleeper(callback):
  i = 0
  while i <= 10:
    print i
    time.sleep(1)
    i += 1
  callback('DONE')


class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self):
        response = yield tornado.gen.Task(sleeper)
        self.write(response)
        self.finish()

class OtherHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('hello world')
        print 'in other'
        self.finish()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM