
在 flask 中使用线程执行耗时模型训练时,`render_template()` 无法从后台线程直接返回响应;必须通过主线程完成渲染,推荐使用全局缓存 + 轮询或 celery 等任务队列实现真正的异步响应。
你当前的代码逻辑存在一个根本性误解:HTTP 响应只能由处理请求的主线程(即 inputs() 视图函数)生成并返回给客户端。train_models() 在新线程中运行,即使它调用了 render_template("visualize.html"),该返回值也仅是一个字符串对象,被丢弃在后台线程中,完全不会发送给浏览器——这正是 visualize.html 从未显示的原因。
✅ 正确做法:分离“触发”与“获取结果”两个阶段
你需要将流程拆解为:
- 前端提交 → 后端启动训练任务(立即返回 loading 页面)
- 前端轮询(或 WebSocket)检查任务状态
- 后端提供独立接口返回训练结果(HTML 或 JSON)
下面是一个轻量、无需额外服务(如 Redis/Celery)的可行方案:
✅ 方案一:使用内存缓存(适用于单进程开发/测试)
from flask import Flask, render_template, request, jsonify
import threading
import time
import uuid
from collections import defaultdict
app = Flask(__name__)
# 全局任务存储:task_id → {"status": "running"/"done", "html": "...", "plots": {...}}
TASKS = {}
@app.route("/inputs", methods=["POST"])
def inputs():
file = request.files.get("dataset")
algo = request.form.getlist("algo")
if not file:
return render_template("input_form.html", error="Please upload a CSV file", algos=ALGO, selected_algo=algo)
data = io.StringIO(file.stream.read().decode("UTF8"), newline=None)
dataset = pd.read_csv(data)
task_id = str(uuid.uuid4())
TASKS[task_id] = {"status": "running", "html": None}
# 启动后台训练(传入 task_id 用于写回结果)
train_thread = threading.Thread(
target=train_models,
args=(app, algo, dataset, task_id)
)
train_thread.daemon = True
train_thread.start()
return render_template("loading.html", task_id=task_id)更新 train_models 函数,不渲染模板,只生成内容并存入缓存:
def train_models(app, algo, dataset, task_id):
print("Starting training...")
with app.app_context():
plot_model = Gen_Plot()
plots = {}
selected_algo = {}
for model in algo:
# ... 训练 & 生成图表逻辑 ...
pass
# ✅ 关键:生成 HTML 字符串,存入全局 TASKS
result_html = render_template("visualize.html", plots=plots, selected_algo=selected_algo)
TASKS[task_id] = {
"status": "done",
"html": result_html
}
print(f"Task {task_id} completed and HTML cached.")✅ 前端轮询接口(/task_status/)
@app.route("/task_status/")
def task_status(task_id):
task = TASKS.get(task_id)
if not task:
return jsonify({"status": "not_found"}), 404
if task["status"] == "done":
return jsonify({"status": "done", "html": task["html"]})
return jsonify({"status": "running"}) ✅ 前端 loading.html 添加轮询逻辑(JavaScript)
Training models... please wait.
⚠️ 注意事项与进阶建议
- 线程安全:TASKS 是简单字典,在单进程下可用;若部署多 Worker(如 Gunicorn),需改用 Redis 或数据库共享状态。
- 内存泄漏:定期清理 TASKS 中过期任务(例如添加时间戳 + 后台清理线程)。
- 生产环境强烈推荐 Celery + Redis:支持任务重试、优先级、进度跟踪、错误日志等,参考官方教程。
- 不要在后台线程中调用 render_template 并期望它“跳转”或“响应” —— Flask 的请求上下文(request, session, g)在线程间不共享,且响应流已由主线程关闭。
通过以上改造,你既能保持用户界面友好(显示加载页),又能确保结果 HTML 正确送达浏览器,真正实现“后台训练 + 前端按需展示”的异步体验。










