windsong

xxxx的个人博客

马上订阅 windsong RSS 更新: https://www.windsong.top/atom.xml

Web 后台任务管理

2025年8月11日 16:30

Web 后台任务管理

在 Web 开发中,经常会遇到这样的场景:用户点击一个按钮,背后需要执行一个耗时几秒甚至几分钟的操作,比如生成复杂报表、处理上传的视频、调用一个缓慢的第三方 API,或者运行数值求解器做大规模仿真。解决这类问题的核心思路是将耗时操作交给后台异步执行,让 Web 服务快速响应用户请求。为了达到这个目的,可以有多种方案,比如单线程同步执行、子进程异步执行、或者引入消息队列,通过中间件协调 Web 服务和后台任务进程等。这篇文章结合三个案例,从最简单的串行执行方式到基于 Redis + RQ 的任务队列,初步梳理后台任务管理的常见思路。这个文章暂时不涉及守护进程配置、任务结果持久化和后续任务控制等方面的内容,这些内容在后续的文章中整理。

项目准备

我们将构建一个极简的 Flask 应用,它有两个接口: * /task: 触发一个模拟的耗时任务。 * /health: 一个能立即返回的健康检查接口,用来检测我们的服务器是否“活着”。

我们创建一个简单的shell脚本来模拟一个需要10秒钟才能完成的任务。

long_task.sh:

1
2
3
4
#!/bin/bash
echo "后台任务开始... (PID: $$)"
sleep 10
echo "后台任务完成!"

在 Shell 脚本中,$$ 表示当前执行脚本或 Shell 的 PID(Process ID)。别忘了给它执行权限:chmod +x long_task.sh

案例一:同步阻塞

Werkzeug 实现

我们直接在API里调用任务并等待它完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# case1_app.py

import subprocess
from flask import Flask

app = Flask(__name__)

@app.route('/task')
def create_task():
print("收到创建任务请求,开始同步执行...")
# subprocess.run 是阻塞的
result = subprocess.run(['./long_task.sh'], capture_output=True, text=True)
print("同步任务执行完成。")
return {
"message": "任务已同步完成",
"output": result.stdout
}

@app.route('/health')
def health_check():
return "OK"

在 Flask 开发服务器(Werkzeug)中,默认是使用多线程模式的。也就是说,即使某个请求处理过程中发生了阻塞(比如执行一个长时间运行的子进程),也不会阻塞整个服务器的进程,其他请求依然可以由其他线程并发处理,不会受到影响。例如,下面这个命令启动了一个默认的 Flask 开发服务器(开启了多线程):

1
flask --app case1_app run --host=0.0.0.0 --port=9090

此时访问 /task 会同步执行脚本,但仍然可以同时访问 /health 获取立即响应。

为了演示阻塞对整个服务器的影响,我们可以强制关闭多线程模式,将服务器改成串行运行:

1
flask --app case1_app run --host=0.0.0.0 --port=9090 --without-threads

此时,在浏览器或 curl 中访问 http://127.0.0.1:9090/task,会发现这个请求“卡住”了10秒钟才返回结果。在卡住的这10秒内,如果访问 http://127.0.0.1:9090/health,会发现接口没有响应,直到 /task 执行完成,/health 才会返回 "OK"。这说明所有请求都被串行处理了,整个服务被阻塞。

1
2
3
4
收到创建任务请求,开始同步执行...
同步任务执行完成。
127.0.0.1 - - [07/Aug/2025 14:19:43] "GET /task HTTP/1.1" 200...

剩余内容已隐藏

查看完整文章以阅读更多