python 启动异步任务,直接返回响应,然后异步执行长时间执行

“收到请求立刻返回,长任务在后台慢慢跑”。常见有两条路:
1. 轻量级:直接用 asyncio/FastAPI 自带的后台任务;
2. 生产级:把长任务丢到任务队列(Celery/RQ)里由独立 Worker 跑。

下面给你两套最实用的实现。

方案 A:FastAPI + asyncio.create_task(轻量、零依赖)

适合 I/O 密集型或中等耗时任务;进程挂了任务会丢,不跨多进程共享状态。

# app.py
import asyncio, uuid
from fastapi import FastAPI, Response
from pydantic import BaseModel

app = FastAPI()

# 简易内存任务表:生产建议换 Redis
TASKS: dict[str, dict] = {}
HANDLES: dict[str, asyncio.Task] = {}

class JobIn(BaseModel):
    x: int

async def long_io_job(task_id: str, x: int):
    try:
        TASKS[task_id] = {"status": "running", "progress": 0}
        for i in range(5):           # 模拟分阶段进度
            await asyncio.sleep(2)    # 模拟耗时 I/O
            TASKS[task_id]["progress"] = (i + 1) * 20
        TASKS[task_id].update({"status": "succeeded", "result": x * 2})
    except asyncio.CancelledError:
        TASKS[task_id]["status"] = "cancelled"
        raise
    except Exception as e:
        TASKS[task_id].update({"status": "failed", "error": repr(e)})

@app.post("/jobs", status_code=202)
async def submit(job: JobIn, response: Response):
    task_id = str(uuid.uuid4())
    TASKS[task_id] = {"status": "queued", "progress": 0}
    handle = asyncio.create_task(long_io_job(task_id, job.x))
    HANDLES[task_id] = handle
    # 202 Accepted + Location 指向查询接口
    response.headers["Location"] = f"/jobs/{task_id}"
    return {"task_id": task_id}

@app.get("/jobs/{task_id}")
async def status(task_id: str):
    return TASKS.get(task_id, {"status": "not_found"})

@app.delete("/jobs/{task_id}")
async def cancel(task_id: str):
    h = HANDLES.get(task_id)
    if h and not h.done():
        h.cancel()
        return {"task_id": task_id, "cancelled": True}
    return {"task_id": task_id, "cancelled": False}

启动:

uvicorn app:app --host 0.0.0.0 --port 8000 --workers 1

要点 / 踩坑
• 用 --workers 1,否则多进程时内存字典不共享(多 worker 必须用 Redis/Mongo 等做状态存储)。
• CPU 密集任务别直接 await,用 run_in_executor 或进程池(见下)。
• 服务重启会丢任务;要持久化请上方案 B。

CPU 密集型任务写法(不阻塞事件循环)

import concurrent.futures, os

PROCESS_POOL = concurrent.futures.ProcessPoolExecutor(max_workers=os.cpu_count())

def cpu_heavy(n: int) -> int:
    # 很耗 CPU 的计算
    s = 0
    for i in range(10**8):
        s += (i * n) % 97
    return s

async def long_cpu_job(task_id: str, n: int):
    loop = asyncio.get_running_loop()
    TASKS[task_id] = {"status": "running"}
    try:
        result = await loop.run_in_executor(PROCESS_POOL, cpu_heavy, n)
        TASKS[task_id] = {"status": "succeeded", "result": result}
    except Exception as e:
        TASKS[task_id] = {"status": "failed", "error": repr(e)}

方案 B:FastAPI + Celery(生产可落地,支持重试/持久化/多 worker)

适合真正“长 & 多”的任务;跨进程、可水平扩展、进程挂了不丢、可监控。

  1. 安装与目录
pip install celery[redis] redis fastapi uvicorn
# 需要一个 Redis(或 RabbitMQ)作为 broker/backend
  1. 定义 Celery 应用与任务
# celery_app.py
from celery import Celery

celery_app = Celery(
    "myapp",
    broker="redis://localhost:6379/0",
    backend="redis://localhost:6379/0",
)

@celery_app.task(bind=True, autoretry_for=(Exception,), retry_backoff=True, max_retries=3)
def long_task(self, x: int):
    import time
    for i in range(5):
        time.sleep(2)               # 模拟耗时
        self.update_state(state="PROGRESS", meta={"progress": (i+1)*20})
    return {"result": x * 2}
  1. FastAPI 路由
# app.py
from fastapi import FastAPI, Response
from pydantic import BaseModel
from celery.result import AsyncResult
from celery_app import celery_app, long_task

app = FastAPI()

class JobIn(BaseModel):
    x: int

@app.post("/jobs", status_code=202)
async def submit(job: JobIn, response: Response):
    async_res = long_task.delay(job.x)      # 丢给 Celery
    response.headers["Location"] = f"/jobs/{async_res.id}"
    return {"task_id": async_res.id}

@app.get("/jobs/{task_id}")
async def status(task_id: str):
    r = AsyncResult(task_id, app=celery_app)
    if r.state == "PENDING":
        return {"status": "queued"}
    if r.state == "PROGRESS":
        return {"status": "running", "progress": r.info.get("progress")}
    if r.state == "SUCCESS":
        return {"status": "succeeded", "result": r.result}
    if r.state == "FAILURE":
        return {"status": "failed", "error": str(r.result)}
    return {"status": r.state}
  1. 启动
# 启动 Celery worker(可多进程/多机横向扩展)
celery -A celery_app.celery_app worker --loglevel=INFO --concurrency=4

# 启动 API
uvicorn app:app --host 0.0.0.0 --port 8000 --workers 2

优点
• 任务独立于 API 进程;API 只负责“接任务 & 查状态”,能快速返回 202 Accepted。
• Celery 支持重试、失败告警、计划任务、结果持久化、并发调度。
• 可结合 Flower(pip install flower)做 Web 监控:celery -A celery_app.celery_app flower。

选型建议
• 简单场景/内部服务:方案 A(asyncio.create_task/BackgroundTasks)最快落地。
• 需要持久化、重试、跨进程/多机:方案 B(Celery/RQ/Dramatiq)。
• CPU 密集:务必用进程池或独立 worker(不要堵住事件循环)。
• 多 Gunicorn/Uvicorn workers:不要用“内存字典”保存任务状态,改用 Redis。
• 返回协议:用 202 Accepted + Location 头,并提供 /jobs/{id} 轮询;需要实时推送可加 SSE/WebSocket。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MonkeyKing.sun

对你有帮助的话,可以打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值