Подтвердить что ты не робот

Асинхронные запросы блокировки торнадо

Используя Tornado, у меня есть запрос Get, который занимает много времени, так как он делает много запросов к другому веб-сервису и обрабатывает данные, может потребоваться несколько минут для полного завершения. Я не хочу, чтобы это блокировало весь веб-сервер от ответа на другие запросы, которые он в настоящее время делает.

Как я понимаю, Tornado является однопоточным и выполняет каждый запрос синхронно, даже если он обрабатывает их асинхронно (все еще запутанный на этом бите). Есть части длинного процесса, который может быть точкой паузы, чтобы сервер мог обрабатывать другие запросы (возможное решение?). Я запускаю его на Heroku с одним рабочим, поэтому не уверен, как это переводится в размножение нового потока или многопроцессорности, с которыми у меня нет опыта работы с python.

Вот что я пытаюсь сделать: клиент делает вызов get для запуска процесса, затем я прохожу через другой вызов каждые 5 секунд, чтобы проверить статус и обновить страницу с помощью новой информации (длинный опрос также будет работают, но сталкиваются с тем же вопросом). Проблема заключается в том, что запуск длинного процесса блокирует все новые запросы на получение (или новые длинные сеансы опроса) до тех пор, пока он не завершится.

Есть ли простой способ начать этот длинный вызов и не блокировать его весь веб-сервер? Есть ли что-нибудь, что я могу добавить в код, чтобы сказать.. "пауза, перейдите в ожидающие запросы, затем продолжите"?

Мне нужно инициировать запрос получения на ProcessHandler. Затем мне нужно продолжить запрос StatusHandler во время работы ProcessHandler.

Пример:

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()
4b9b3361

Ответ 1

Здесь представлено полное примерное приложение Tornado, использующее HTTP-клиент Async и модуль gen.Task, чтобы сделать вещи простыми.

Если вы прочитали больше о gen.Task в документах, вы увидите, что вы действительно можете отправлять несколько запросов одновременно. Это использует основную идею Tornado, где все не блокирует и все еще поддерживает один процесс.

Обновление:. Я добавил обработчик потока, чтобы продемонстрировать, как вы могли отправить работу во второй поток и получить 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 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()

Ответ 2

Решение koblas велико. Вот альтернатива, которая использует 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()