You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用fastapi_utilities的repeat_at报错:预期协程,却得到None

解决FastAPI中使用repeat_at装饰器后asyncio.create_task报错TypeError的问题

错误原因

核心问题出在@repeat_at装饰器的行为上:这个装饰器的作用是自动将目标函数注册为FastAPI的后台定时任务,它不会返回原协程函数,装饰后call_api变量指向的是装饰器的返回值(通常是None)。所以当你调用call_api()时,得到的不是协程对象而是None,传入asyncio.create_task()自然就会抛出TypeError: a coroutine was expected, got None。

同时代码中还有两个额外问题:

  • 混用repeat_at自动定时和手动create_task触发的逻辑,二者存在冲突;
  • call_api内部的while True+break代码完全冗余,无实际作用。

解决方案

根据需求分两种场景处理:

场景1:仅需分钟/小时级定时调度

直接依赖repeat_at的自动调度能力,FastAPI启动后装饰器会自动按cron规则执行任务,无需手动触发接口。

修改后的代码:

import requests
from fastapi_utilities import repeat_at
from fastapi_offline import FastAPIOffline
import asyncio

app = FastAPIOffline()

# repeat_at装饰器自动注册定时任务,启动后每分钟执行一次
@repeat_at(cron="*/1 * * * *")
async def scheduled_call_api():
    url = "http://sssssss/both/execute_query/4"
    try:
        # 用run_in_executor处理同步的requests请求,避免阻塞事件循环
        loop = asyncio.get_event_loop()
        response = await loop.run_in_executor(None, lambda: requests.get(url))
        response_json = response.json()
        print(response_json)
        return response_json
    except requests.exceptions.RequestException as e:
        print(f"Error fetching data: {e}")

@app.get("/")
async def root():
    return {"message": "Scheduled task is running in background"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="localhost", port=8001)

场景2:需要手动触发+定时调度并存

将核心业务逻辑抽离为独立协程函数,避免被repeat_at装饰器覆盖,同时分别实现定时任务和手动触发逻辑。

修改后的代码:

import requests
from fastapi_utilities import repeat_at
from fastapi_offline import FastAPIOffline
import asyncio

app = FastAPIOffline()
manual_task = None

# 独立的核心业务协程,不被装饰器修改
async def call_api():
    url = "http://sssssss/both/execute_query/4"
    try:
        loop = asyncio.get_event_loop()
        response = await loop.run_in_executor(None, lambda: requests.get(url))
        response_json = response.json()
        print(response_json)
        return response_json
    except requests.exceptions.RequestException as e:
        print(f"Error fetching data: {e}")

# 定时任务调用核心函数,每分钟执行一次
@repeat_at(cron="*/1 * * * *")
async def scheduled_task():
    await call_api()

# 手动触发接口
@app.get("/trigger-call-api")
async def trigger_call_api():
    global manual_task
    if manual_task is None or manual_task.done():
        manual_task = asyncio.create_task(call_api())
        return {"message": "call_api triggered manually"}
    else:
        return {"message": "call_api is already running manually"}

# 手动停止接口
@app.get("/stop-call-api")
async def stop_call_api():
    global manual_task
    if manual_task is not None and not manual_task.done():
        manual_task.cancel()
        manual_task = None
        return {"message": "Manual call_api task stopped"}
    else:
        return {"message": "No running manual call_api task"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="localhost", port=8001)

内容的提问来源于stack exchange,提问作者Hosein Sargoli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 03:10:35