简体   繁体   English

将 anyio.TaskGroup 与 fastapi.StreamingResponse 一起使用

[英]Use anyio.TaskGroup with fastapi.StreamingResponse

anyio is a part of starlette and, therefore, of FastAPI. anyiostarlette的一部分,因此也是 FastAPI 的一部分。 I find it quite convenient to use its task groups to perform concurrent requests to external services outside of one of my API servers.我发现使用它的任务组对我的一台 API 服务器之外的外部服务执行并发请求非常方便。

Also, I would like to stream out the results as soon as they are ready.另外,我想把 stream 准备好后尽快拿出结果。 fastapi.StreamingResponse could do the trick, still I need to be able to keep the task group up and running after returning StreamingResponse , but it sounds like something that goes against the idea of structured concurrency . fastapi.StreamingResponse可以解决问题,但在返回StreamingResponse后我仍然需要能够保持任务组正常运行,但这听起来与结构化并发的想法背道而驰。

Using an asynchronous generator may look like an obvious solution, but yield in general can not be used in a task group context, according to this: https://trio.readthedocs.io/en/stable/reference-core.html#cancel-scopes-and-nurseries使用异步生成器可能看起来是一个明显的解决方案,但一般来说, yield不能在任务组上下文中使用,根据这个: https://trio.readthedocs.io/en/stable/reference-core.html#cancel -范围和托儿所

There is an example of a FastAPI server that seems to work, though it aggregates the responses before returning them:有一个 FastAPI 服务器的示例似乎可以工作,尽管它会在返回之前聚合响应:

import anyio
from fastapi import FastAPI
from fastapi.responses import StreamingResponse


app = FastAPI()


@app.get("/")
async def root():
    # What to put below?
    result = await main()
    return StreamingResponse(iter(result))


async def main():
    send_stream, receive_stream = anyio.create_memory_object_stream()

    result = []
    async with anyio.create_task_group() as tg:
        async with send_stream:
            for num in range(5):
                tg.start_soon(sometask, num, send_stream.clone())

        async with receive_stream:
            async for entry in receive_stream:
                # What to do here???
                result.append(entry)

    return result


async def sometask(num, send_stream):
    await anyio.sleep(1)
    async with send_stream:
        await send_stream.send(f'number {num}\n')



if __name__ == "__main__":
    import uvicorn
    # Debug-only configuration
    uvicorn.run(app)

So, the question is, is there something similar to @trio_util.trio_async_generator in anyio , or is it possible to use @trio_util.trio_async_generator with FastAPI directly?所以,问题是,在 anyio 中是否有类似于@trio_util.trio_async_generatoranyio ,或者是否可以直接将@trio_util.trio_async_generator与 FastAPI 一起使用?

Maybe there are other solutions?也许还有其他解决方案?

import anyio
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()


@app.get("/")
async def root():
    return StreamingResponse(main())


async def main():
    send_stream, receive_stream = anyio.create_memory_object_stream()

    async with anyio.create_task_group() as tg:
        async with send_stream:
            for num in range(5):
                tg.start_soon(sometask, num, send_stream.clone())

        async with receive_stream:
            async for entry in receive_stream:
                yield entry


async def sometask(num, send_stream):
    async with send_stream:
        for i in range(1000):
            await anyio.sleep(1)
            await send_stream.send(f"number {num}\n")


if __name__ == "__main__":
    import uvicorn

    # Debug-only configuration
    uvicorn.run(app)

unexpectedly, it works.出乎意料的是,它起作用了。

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

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