Запустите Торнадо вместе с еще одним долгосрочным заданием Asyncio - PullRequest
0 голосов
/ 01 марта 2020

Я бы хотел запустить сервер Tornado вместе с независимой долгосрочной задачей в asyncio в Python 3.7. Я новичок в asyncio. Я читал, что вы должны вызывать asyncio.run() только один раз, поэтому я собрал обе задачи в рамках метода main(), чтобы я мог передать один аргумент asyncio.run(). Когда я запускаю этот код, я получаю ошибку TypeError: a coroutine was expected, got <function start_tornado at 0x105c8e6a8>. Я хотел бы, чтобы код работал без ошибок, но в конечном итоге я хочу знать, как сделать это правильно. Код, который я написал ниже, выглядит как уродливый хак.

import asyncio
import datetime
import tornado.ioloop
import tornado.web


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

# Fake background task I got from here:
# https://docs.python.org/3/library/asyncio-task.html#sleeping
async def display_date():
    while True:
        print(datetime.datetime.now())
        await asyncio.sleep(1)

async def start_tornado():
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

async def main():
    await asyncio.create_task(start_tornado)
    await asyncio.create_task(display_date)

asyncio.run(main())

Ответы [ 2 ]

2 голосов
/ 01 марта 2020
  1. Когда вы используете create_task с функцией async def, вызовите функцию как обычно и затем передайте результат в create_task.

    await asyncio.create_task(start_tornado())
    await asyncio.create_task(display_date())
    
  2. Вам не нужно использовать create_task, если вы собираетесь немедленно await. Используйте create_task без await для запуска задач в фоновом режиме, например display_date(). start_tornado не является фоновой задачей в этом смысле, потому что он не имеет бесконечного l oop, он просто запускает сервер, который Tornado переводит в фоновый режим. Поэтому я написал бы это так:

    await start_tornado()
    asyncio.create_task(display_date())
    
  3. Начиная с Tornado 5.0, события Tornado IOL oop и asyncio l oop интегрированы по умолчанию, поэтому вам нужно только начать один, а не оба. Так что просто удалите вызов IOLoop.start() в start_tornado.

  4. start_tornado в настоящее время не делает ничего асинхронного, поэтому это может быть просто нормальная функция. Но было бы также разумно добавить асинхронную логику запуска c, например, установление соединений с базой данных, чтобы вы могли сохранить ее в качестве сопрограммы.

Рабочая версия кода с моими правками: https://repl.it/@bdarnell / FarawayAdmiredConversions

0 голосов
/ 02 марта 2020

Торнадо может уже иметь все, что вам нужно.

  1. Вместо await asyncio.create_task вы можете использовать tornado.ioloop.IOLoop.current().add_callback
  2. Вместо await asyncio.sleep(1) вы можете использовать yield gen.sleep(1)

В итоге весь код из вашего примера может выглядеть так:

import datetime
import tornado.ioloop
import tornado.web
import tornado.gen as gen


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")


def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])


@gen.coroutine
def display_date():
    while True:
        print(datetime.datetime.now())
        yield gen.sleep(1)


def start_tornado():
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().add_callback(display_date)
    tornado.ioloop.IOLoop.current().start()


start_tornado()
...