「FastAPI & SQLAlchemy 实战」系列共 10 篇,从协程基础讲到数据库接口。这是第 3 篇。
结论:create_task 负责启动并发,gather 负责等待结果,wait_for 负责超时,cancel 负责取消。四个工具解决协程的四种控制需求。
一、create_task:把协程变成任务
import asyncio
async def my_func(name, delay):
await asyncio.sleep(delay)
return f"结果 of {name}"
async def main():
task1 = asyncio.create_task(my_func("C", 1))
task2 = asyncio.create_task(my_func("D", 2))
results = await asyncio.gather(task1, task2)
print(results) # ['结果 of C', '结果 of D']
create_task 会立即把协程注册到事件循环,两个任务同时开始;gather 只是等待它们结束。总耗时约 2 秒,不是 3 秒。
二、超时:wait_for
async def slow_task():
await asyncio.sleep(5)
async def main():
try:
await asyncio.wait_for(slow_task(), timeout=2.0)
except asyncio.TimeoutError:
print("任务超时")
超时后等待对象会被取消,必须捕获 TimeoutError,否则异常会向上抛。
三、取消:Task.cancel
async def worker():
try:
await asyncio.sleep(10)
except asyncio.CancelledError:
print("清理资源")
raise
task = asyncio.create_task(worker())
await asyncio.sleep(1)
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
取消不是“立即杀死”,而是给协程一个抛出 CancelledError 的机会;捕获后要做资源清理,再重新抛出。
四、阻塞代码:to_thread
协程里调用 requests.get()、time.sleep()、同步数据库驱动,会阻塞整个事件循环。用 asyncio.to_thread() 把它扔到线程池:
import requests
async def fetch(url):
response = await asyncio.to_thread(requests.get, url)
return response.status_code
五、并发请求实战
import aiohttp
async def fetch_url(session, url):
async with session.get(url) as response:
await response.text()
return url
async def main():
urls = ["https://www.baidu.com", "https://www.bing.com", "https://www.github.com"]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
print(results)
总耗时约等于最慢的那个请求,而不是三个请求相加。
六、常见错误
- 只
create_task不await:顶级协程结束后子任务被取消; - 在异步函数里用同步库:事件循环被阻塞,所有请求排队;
- 取消后不做清理:连接、文件句柄可能泄漏;
- 不处理
TimeoutError:超时变成 500。
下一篇
协程基础讲完,下一篇进入 Web:WSGI 和 ASGI 的区别,以及 FastAPI 为什么必须配 Uvicorn。
上一篇:async/await 与事件循环 | 下一篇:WSGI 与 ASGI

AI项目博客






