Я хотел бы запустить сервер 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())
0
user554481 1 Мар 2020 в 04:42

2 ответа

Лучший ответ
  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 не является фоновой задачей в этом смысле, потому что он не имеет бесконечного цикла, он просто запускает сервер, который Tornado переводит в фоновый режим. Так что я бы написал это так:

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

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

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

2
Ben Darnell 1 Мар 2020 в 18:02

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

  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()
0
GSazheniuk 2 Мар 2020 в 20:01