前言
回顾一下客户端发送网络请求的过程。
同步请求
如常见的requests发起的请求。
import requests
def send_sync_request(url, data):
response = requests.post(url, data=data)
return response.text
def main():
url = 'https://example.com/api/endpoint'
data = {'key': 'value'}
response = send_sync_request(url, data)
print(response)
# 运行主函数
main()
如果需要同时发送多个,可以采用多线程并发。
import requests
import threading
def send_sync_request(url, data):
response = requests.post(url, data=data)
return response.text
def main():
url = 'https://example.com/api/endpoint'
data = {'key': 'value'}
threads = []
# 并发2个请求
nums = 2
for i in range(nums):
t = threading.Thread(target=send_sync_request, args=(url, data))
threads.append(t)
for i in range(nums):
threads[i].start()
for i in range(nums):
threads[i].join()
print("结束")
# 运行主函数
main()
异步请求
requests不支持异步请求,因此可以采用aiohttp来做。
import asyncio
import aiohttp
async def make_async_request(url, data):
async with aiohttp.ClientSession() as session:
async with session.post(url=url, data=data) as response:
return await response.read()
async def main():
url = 'https://example.com/api/endpoint'
data = {'key': 'value'}
response = await make_async_request(url, data)
print(response)
# 运行主函数
await main()
并发多个请求,可以直接用asyncio.gather()
发起。
import asyncio
import aiohttp
async def make_async_request(url, data):
async with aiohttp.ClientSession() as session:
async with session.post(url=url, data=data) as response:
return await response.read()
async def main():
url = 'https://example.com/api/endpoint'
data = {'key': 'value'}
# 并发2个
await asyncio.gather(
make_async_request(url, data),
make_async_request(url, data)
)
# 运行主函数
await main()
服务端应用响应
同步与异步请求
通过fastapi定义一个简单的应用,获取计算结果后返回给客户端。
from fastapi import FastAPI
app = FastAPI()
@app.post("/sync_endpoint")
def sync_endpoint():
# 执行同步操作
result = do_sync_task()
# 返回同步操作的结果
return {"result": result}
def do_sync_task():
# 模拟同步操作等待两秒钟
time.sleep(2)
return "Sync Task Completed"
如果处理时间过长,比如需要等待IO,则可以改为异步任务,在中间穿插其他任务的执行,以此提高服务端的响应能力。
from fastapi import FastAPI
app = FastAPI()
@app.post("/async_endpoint")
async def async_endpoint():
# 执行异步操作
result = do_async_task()
# 夹杂其他操作
# ...
# 等待完成
await result
# 返回异步操作的结果
return {"result": result}
async def do_async_task():
# 模拟异步操作,比如发送异步请求、访问数据库等
await asyncio.sleep(2) # 模拟等待两秒钟
return "Async Task Completed"
后台任务
以上的服务,不管是异步还是同步,服务端都会占用响应,直到返回结果前都不会结束。而在 fastapi 中,可以使用后台任务(Background Tasks)来异步执行一些耗时的操作,而无需等待其完成。后台任务非常适用于需要进行一些异步处理的场景,例如发送电子邮件、处理图像、推送通知等。
要使用后台任务,需要导入 BackgroundTasks 类,并在路由处理函数中将其作为一个参数传递。然后,可以使用 background 对象的 add_task()
方法来添加后台任务。
from fastapi import BackgroundTasks, FastAPI
import time
app = FastAPI()
def write_notification(email: str, message=""):
with open("log.txt", mode="w") as email_file:
content = f"notification for {email}: {message}"
time.sleep(3)
email_file.write(content)
@app.get("/send-notification")
async def send_notification(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(write_notification, email, message="some notification")
return {"message": "Notification sent in the background"}
在 FastAPI 中使用后台任务来处理一些耗时的操作可以提高应用程序的响应性能。但是,后台任务不适合需要返回结果给客户端的场景,因为客户端无法立即获取到后台任务的结果。
如果一定要用后台任务来处理需要返回结果的任务,也是可以完成的,比如添加一些ID信息用于后续程序获取原任务结果。一个简单的例子如下。
result = {}
@app.get("start_task")
def start_task(params, background_tasks: BackgroundTasks):
def bg_task(task_id, params):
result[task_id] = func(params) # 某个耗时的函数
task_id = uuid.uuid4().hex
background_tasks.add_task(bg_task, task_id, params)
return {"submit task id is":task_id}
@app.get("/get_result")
def get_result(task_id):
if task_id in result:
return {"result": result[task_id]}
return {"result":"task not finish"}
在上面的例子,客户端发送请求后,会马上得到 “submit task id is:xxx” 这个结果,但耗时函数的结果并没有同步返回。当需要耗时函数的结果时,可以通过发送另一个get请求get_result
,在此之前可以做一些其他的操作,从而提高客户端的程序响应能力。