python3.5: с помощью aiohttp можно одновременно обслуживать несколько ответов?

Я использую последнюю версию (1.0.2) aiohttp с python3.5. У меня есть следующий код сервера.

import asyncio

from aiohttp.web import Application, Response, StreamResponse, run_app


async def long(request):
    resp = StreamResponse()
    name = request.match_info.get('name', 'Anonymous')
    resp.content_type = 'text/plain'
    for _ in range(1000000):
        answer = ('Hello world\n').encode('utf8')

        await resp.prepare(request)
        resp.write(answer)
    await resp.write_eof()
    return resp


async def init(loop):

    app = Application(loop=loop)
    app.router.add_get('/long', long)
    return app

loop = asyncio.get_event_loop()
app = loop.run_until_complete(init(loop))
run_app(app)

Если я затем запущу два curl-запроса curl http://localhost:8080/long в разных терминалах, только первый получит данные

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

Большая часть кода, который я нашел в Интернете о concurent + asyncio, говорит только о стороне клиента, но не о стороне сервера.

Я что-то упустил или мое понимание того, как работает asyncio, ошибочно?


person allan.simon    schedule 24.09.2016    source источник


Ответы (1)


Просто нажмите await resp.drain() после resp.write(), чтобы дать aiohttp возможность переключаться между задачами:

import asyncio

from aiohttp.web import Application, Response, StreamResponse, run_app


async def long(request):
    resp = StreamResponse()
    name = request.match_info.get('name', 'Anonymous')
    resp.content_type = 'text/plain'
    await resp.prepare(request)  # prepare should be called once
    for _ in range(1000000):
        answer = ('Hello world\n').encode('utf8')

        resp.write(answer)
        await resp.drain()  # switch point
    await resp.write_eof()
    return resp


async def init(loop):

    app = Application(loop=loop)
    app.router.add_get('/long', long)
    return app

loop = asyncio.get_event_loop()
app = loop.run_until_complete(init(loop))
run_app(app)
person Andrew Svetlov    schedule 24.09.2016
comment
спасибо, по какой-то причине мой мозг начал думать, что await resp.prepare(request) это то, что делает точку переключения. Однако с вашим кодом из двух моих завитков я все равно получил только один прием данных - person allan.simon; 24.09.2016
comment
Возможно, причина в следующем: 'Hello world\n' слишком короткий для переполнения выходного буфера. Добавление await asyncio.sleep(0) явно переключит задачу. - person Andrew Svetlov; 24.09.2016
comment
действительно, теперь, когда я добавил asyncio.sleep(0), мои 2 завитка получают байты (работает также, если я удаляю вызов drain), должен ли я предпочесть это решение в случае рабочей нагрузки, когда небольшой пакет байтов отправляется один раз? - person allan.simon; 24.09.2016
comment
sleep(0) сразу переключает задачу. drain() переключается, только если выходной буфер перегружен. Таким образом, sleep() помогает для небольших пакетов данных, но drain рекомендуется для реальных случаев использования, таких как загрузка 10-мегапиксельных файлов. - person Andrew Svetlov; 24.09.2016
comment
Ясно, значит ли это, что в моем случае функция стока() окончательно переключится после N-й итерации цикла с достаточно большим N? - person allan.simon; 24.09.2016
comment
да, переключение произойдет после перегрузки выходного буфера (достижение верхней отметки). - person Andrew Svetlov; 25.09.2016