Skip to main content

funboost-async-programming

当需要在 funboost 中使用异步(async/await)编程时使用。触发场景:async def 消费函数、aio_push/aio_publish、concurrent_mode=ASYNC、event loop 报错。关键词:async, await, asyncio, aio_push, aio_publish, ConcurrentModeEnum.ASYNC, 异步消费, 协程。

ソース情報

リポジトリ
ydf0509/funboost
ソースの最終更新活動
2026年8月4日 14:03
検出された SKILL.md の言語
中国語
スター
892
フォーク
165

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
funboost-async-programming
description
当需要在 funboost 中使用异步(async/await)编程时使用。触发场景:async def 消费函数、aio_push/aio_publish、concurrent_mode=ASYNC、event loop 报错。关键词:async, await, asyncio, aio_push, aio_publish, ConcurrentModeEnum.ASYNC, 异步消费, 协程。
# Funboost 异步编程 ## 概述 funboost 对 asyncio 生态有直接支持:**消费**支持 `async def` 函数,**发布**支持 `aio_push` / `aio_publish`,**RPC 结果**支持 `AioAsyncResult`。远超 Celery 对 async 的支持。 **核心原则:** 异步消费函数用 `async def`;异步发布用 `await func.aio_push()`;异步获取 RPC 结果用 `await AioAsyncResult(...).result`,**禁止**在 async 环境中用同步的 `AsyncResult.result`(会阻塞 event loop)。 ## 适用场景 - 消费函数内部已是 `async def`(aiohttp、httpx、aiomysql 等) - 在 FastAPI / asyncio 应用中发布 funboost 任务 - 需要 `await` 等待 RPC 结果,而非阻塞 event loop - 高并发 IO 密集型任务,希望用协程而非线程 ## 铁律(绝对不可违反) 1. **异步 RPC 必须用 `AioAsyncResult`** — 禁止在 `async def` 里调用 `async_result.result` 2. **RPC 模式必须设置 `is_using_rpc_mode=True`** — 且需配置 Redis(结果存 Redis)(MEMORY_QUEUE 场景可使用 `publisher.get_future()` / `publisher.get_aio_future()` 替代 Redis RPC) 3. **`async def` 消费函数内禁止同步阻塞 IO** — 如 `time.sleep`、`requests.get`。注意:ASYNC 模式仍支持同步 `def` 消费函数(会在线程池中执行) 4. **启动消费仍是 `func.consume()`** — 不存在 `async_consume`、`aio_consume` 等方法 5. **必须使用 `BoosterParams` 对象** — 禁止向 `@boost` 传递裸参数 ## 速查表 | 操作 | 代码 | |------|------| | 异步消费函数 | `@boost(BoosterParams(..., concurrent_mode=ConcurrentModeEnum.ASYNC))` + `async def` | | 异步发布(业务参数) | `await func.aio_push(x, y)` | | 异步发布(字典 + 控制参数) | `await func.aio_publish({"x": 1}, task_options=TaskOptions(...))` | | 异步 RPC 结果 | `aio_result = await func.aio_push(...); await aio_result.result` | | 按 task_id 异步查结果 | `await AioAsyncResult(task_id).status_and_result` | | 启动消费 | `func.consume()`(同步调用,内部自动跑协程池) | | 解决 loop 绑定问题 | `specify_async_loop=loop` | | 获取任务上下文 | `from funboost import fct; fct.task_id`(async 中同样可用) | ## 1. 异步消费函数的正确写法 ### 1.1 ASYNC 协程模式(推荐用于纯 async 生态) ```python import asyncio from funboost import boost, BoosterParams, BrokerEnum, ConcurrentModeEnum @boost(BoosterParams( queue_name="async_fetch_queue", broker_kind=BrokerEnum.MEMORY_QUEUE, concurrent_mode=ConcurrentModeEnum.ASYNC, # ← 关键:协程并发池 concurrent_num=100, qps=10, )) async def async_fetch(url: str): await asyncio.sleep(0.1) # 模拟 async IO print(f"fetch {url}") return url if __name__ == "__main__": for i in range(5): async_fetch.push(f"https://example.com/{i}") async_fetch.consume() ``` **要点:** - 函数必须是 `async def` - `concurrent_mode=ConcurrentModeEnum.ASYNC` 使用 `AsyncPoolExecutor`,所有协程在同一 loop 中并发 - 函数内**只能**用 `await asyncio.sleep()`,**不能**用 `time.sleep()` ### 1.2 THREADING 模式也能运行 async def(更省心) 默认 `concurrent_mode=ConcurrentModeEnum.THREADING` 时,`FlexibleThreadPool` 会自动检测 `async def` 并在每个线程内临时创建 loop 运行协程。**不必为了 async 函数就切换到 ASYNC 模式**。 ```python from funboost import boost, BoosterParams, BrokerEnum @boost(BoosterParams(queue_name="thread_async_queue", broker_kind=BrokerEnum.MEMORY_QUEUE)) async def my_async_task(x): import asyncio await asyncio.sleep(1) print(x) if __name__ == "__main__": my_async_task.push(1) my_async_task.consume() ``` **选择建议:** - 已有大量 async 库、追求协程轻量 → `ConcurrentModeEnum.ASYNC` - 混合 sync/async 函数、不想处理 loop 问题 → 默认 `THREADING`(不写 concurrent_mode) ## 2. 异步发布 `aio_push` / `aio_publish` 通过 `loop.run_in_executor` 将同步发布转为异步,无需异步消息队列客户端。 ```python import asyncio from funboost import boost, BoosterParams, BrokerEnum, ConcurrentModeEnum, TaskOptions @boost(BoosterParams( queue_name="aio_publish_demo", broker_kind=BrokerEnum.MEMORY_QUEUE, concurrent_mode=ConcurrentModeEnum.ASYNC, )) async def process(x: int): await asyncio.sleep(0.5) print(f"process {x}") async def main(): # 方式1:aio_push — 传业务参数 for i in range(3): await process.aio_push(i) # 方式2:aio_publish — 传字典 + 框架控制参数 await process.aio_publish( {"x": 99}, task_options=TaskOptions(countdown=2), ) if __name__ == "__main__": asyncio.run(main()) process.consume() ``` | 方法 | 入参 | 返回 | |------|------|------| | `await func.aio_push(*args, **kwargs)` | 业务参数 | `AioAsyncResult` | | `await func.aio_publish(msg_dict, task_options=...)` | 字典 + 可选 TaskOptions | `AioAsyncResult` | > 局域网发布通常 < 1ms,在 FastAPI 里偶尔用同步 `push()` 也可接受;对外网 MQ 发布建议用 `aio_push`。 ## 3. 异步 RPC 结果获取 RPC 模式需同时满足:**`is_using_rpc_mode=True`** + **Redis 已配置**。 ```python import asyncio from funboost import boost, BoosterParams, BrokerEnum, ConcurrentModeEnum, AioAsyncResult @boost(BoosterParams( queue_name="async_rpc_add", broker_kind=BrokerEnum.REDIS_ACK_ABLE, concurrent_mode=ConcurrentModeEnum.ASYNC, is_using_rpc_mode=True, # ← 必须 )) async def async_add(x: int, y: int): await asyncio.sleep(0.1) return x + y async def main(): async_add.consume() # 方式1:aio_push 直接返回 AioAsyncResult aio_result = await async_add.aio_push(10, 20) print(await aio_result.result) # 30 # 方式2:同步 push + AioAsyncResult 包装(适合 FastAPI 中已 push 的场景) sync_result = async_add.push(1, 2) status = await AioAsyncResult(sync_result.task_id).status_and_result print(status["result"]) # 3 if __name__ == "__main__": asyncio.run(main()) ``` **MEMORY_QUEUE 专用:** 不依赖 Redis RPC 时,可用 `publisher.get_aio_future()`: ```python future = async_task.publisher.get_aio_future(1, 2) result_status = await future print(result_status.result) ``` ## 4. 同步函数 vs 异步函数:区别与选择 | 维度 | 同步 `def` | 异步 `async def` | |------|-----------|-----------------| | 默认并发模式 | `THREADING`(多线程) | `ASYNC` 或 `THREADING` 均可 | | 函数内 IO | `requests`、`time.sleep` | `httpx`、`await asyncio.sleep` | | 发布方式 | `func.push()` | `await func.aio_push()`(async 环境中) | | RPC 结果 | `async_result.result`(阻塞) | `await AioAsyncResult(...).result` | | 复杂度 | 低,推荐大多数用户 | 高,涉及跨线程 loop 问题 | | 适用 | CPU 密集、简单 IO、混合代码 | 已有 asyncio 生态(FastAPI + aiohttp) | **文档建议:** funboost 对 async 支持很强,但**不鼓励非资深开发者用 async def 定义消费函数**。若业务函数已是 async 写法且复用 async 库,再选 ASYNC 模式。 ## 5. 常见 Event Loop 报错及解决 ### 5.1 `attached to a different loop` **原因:** ASYNC 模式在**子线程**中运行 loop;部分库(aiohttp、aiomysql)的连接池绑定创建时的 loop,跨 loop 使用会报错。httpx、sqlalchemy 通常无此问题。 **解决:** 传递 `specify_async_loop` ```python import asyncio import aiohttp from funboost import boost, BoosterParams, BrokerEnum, ConcurrentModeEnum loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) @boost(BoosterParams( queue_name="aiohttp_task", broker_kind=BrokerEnum.MEMORY_QUEUE, concurrent_mode=ConcurrentModeEnum.ASYNC, specify_async_loop=loop, # ← 共享全局连接池时需要;函数内临时创建可省略 )) async def fetch(url: str): async with aiohttp.ClientSession() as session: async with session.get(url) as resp: return (await resp.text())[:50] ``` **不需要 specify_async_loop 的情况:** 函数内临时创建 Session(如上例),不用全局连接池。 ### 5.2 `context manager should be used inside a task` **原因:** 与 5.1 同类——异步连接池/Session 在错误的 loop 或上下文中使用。 **解决:** 同上,使用 `specify_async_loop`;或在函数内临时创建资源而非共享全局池。 ### 5.3 `RuntimeError: This event loop is already running`(nested loop) **原因:** 主线程 loop 已 `run_forever()`,又在同一线程调用 `asyncio.run()` 或 `loop.run_until_complete()`。 **解决:** - 不要在已运行 loop 的线程里再嵌套 `asyncio.run()` - FastAPI/uvicorn 已管理 loop,业务代码用 `await`,不要额外 `asyncio.run()` - 若需 `specify_async_loop`,注意启动顺序:先配置 booster,再启动 `loop.run_forever()`,避免主/子线程 loop 竞争 ### 5.4 在 async 代码中用 `AsyncResult.result` 阻塞 event loop **现象:** FastAPI 接口卡死,所有请求排队。 **解决:** 改用 `AioAsyncResult` ```python # ❌ 错误 — 阻塞整个 event loop async def api_bad(): r = my_task.push(1) return r.result # ✅ 正确 async def api_good(): r = my_task.push(1) return await AioAsyncResult(r.task_id).result ``` ### 5.5 ASYNC 模式中使用 `time.sleep` / `requests.get` **现象:** 整个协程池被卡死,QPS 骤降。 **解决:** 全部改为 async 等价物(`await asyncio.sleep`、`httpx` / `aiohttp`)。 ## 6. 在 FastAPI / 异步框架中使用 funboost ```python from contextlib import asynccontextmanager from fastapi import FastAPI from funboost import boost, BoosterParams, BrokerEnum, AioAsyncResult, ConcurrentModeEnum @boost(BoosterParams( queue_name="web_async_task", broker_kind=BrokerEnum.REDIS_ACK_ABLE, concurrent_mode=ConcurrentModeEnum.ASYNC, is_using_rpc_mode=True, )) async def handle_order(order_id: int): import asyncio await asyncio.sleep(1) return f"order {order_id} done" @asynccontextmanager async def lifespan(app: FastAPI): handle_order.consume() # 应用启动时启动消费 yield app = FastAPI(lifespan=lifespan) @app.post("/order/{order_id}") async def create_order(order_id: int): # 推荐:aio_push + await result aio_result = await handle_order.aio_push(order_id) return {"result": await aio_result.result} @app.post("/order/sync-push/{order_id}") async def create_order_sync_push(order_id: int): # 可接受:同步 push(通常很快)+ AioAsyncResult 等结果 async_result = handle_order.push(order_id) status = await AioAsyncResult(async_result.task_id).status_and_result return {"result": status["result"] if status else None} ``` **要点:** - 在 `lifespan` / `startup` 中调用 `consume()`,不要在每个请求里启动 - 异步路由中**禁止** `async_result.result`,必须用 `AioAsyncResult` - 也可使用 `funboost.faas.fastapi_router` 一键暴露 HTTP 发布接口(见 funboost-faas-deploy skill) ## 7. 异步消费 + 同步消费混合场景 同一进程可同时运行 async 消费函数和 sync 消费函数,分别 `consume()` 即可: ```python import asyncio import time from funboost import boost, BoosterParams, BrokerEnum, ConcurrentModeEnum @boost(BoosterParams( queue_name="mixed_async_queue", broker_kind=BrokerEnum.MEMORY_QUEUE, concurrent_mode=ConcurrentModeEnum.ASYNC, is_using_rpc_mode=True, )) async def async_worker(x): await asyncio.sleep(0.5) return f"async {x}" @boost(BoosterParams( queue_name="mixed_sync_queue", broker_kind=BrokerEnum.MEMORY_QUEUE, is_using_rpc_mode=True, )) def sync_worker(x): time.sleep(0.5) return f"sync {x}" if __name__ == "__main__": async def publish_all(): for i in range(3): await async_worker.aio_push(i) for j in range(3): sync_worker.push(j) # 同步函数仍用 push asyncio.run(publish_all()) async_worker.consume() sync_worker.consume() ``` **注意:** - 两种函数使用**不同 queue_name**(不同 BoosterParams) - 启动:`func1.consume(); func2.consume()` 连续调用,**不要**用 `threading.Thread` 包装 - async 函数链式调度下游:在 async 消费函数内可 `await downstream.aio_push(...)` ## 8. 完整可运行示例 ### 8.1 无 Redis 依赖(MEMORY_QUEUE + get_aio_future) ```python """funboost 异步编程最小完整示例 — 可直接 python 运行,无需 Redis""" import asyncio import os import time from funboost import boost, BoosterParams, BrokerEnum, ConcurrentModeEnum @boost(BoosterParams( queue_name="skill_async_demo_q", broker_kind=BrokerEnum.MEMORY_QUEUE, concurrent_mode=ConcurrentModeEnum.ASYNC, concurrent_num=20, )) async def async_task(n: int): await asyncio.sleep(0.3) return n * n @boost(BoosterParams( queue_name="skill_sync_demo_q",
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る