本文介绍如何在 flask 应用启动的同时,以守护线程方式运行长期后台任务(如文件系统监听),避免阻塞主线程,并确保应用退出时资源安全释放。
本文介绍如何在 flask 应用启动的同时,以守护线程方式运行长期后台任务(如文件系统监听),避免阻塞主线程,并确保应用退出时资源安全释放。
在 Flask 开发中,常需启动一个持续运行的后台任务(例如监听指定目录的文件变化),该任务应与 Web 服务并行执行,且生命周期与应用一致:随应用启动而启动、随应用终止而优雅停止。直接调用 file_watcher() 会导致主线程被 while True 阻塞,无法启动 Flask 服务;而简单使用 threading.Thread 但未正确配置,又可能引发线程残留或资源泄漏。
✅ 正确做法:守护线程 + 应用生命周期管理
推荐采用 守护线程(daemon thread) + atexit 或 teardown_appcontext 清理机制 的组合方案。以下是经过验证的完整实现:
1. 修改 app.py:启动守护线程并注册清理逻辑
import threading
import atexit
from flask import Flask
from your_package import create_app, init_flask_cmds
from your_package.flask_commands import file_watcher # 注意:需改造为可接受 stop_event 参数
from your_package.file_watcher import Handler
app = create_app("pb")
init_flask_cmds(app)
# 创建线程安全的停止信号
stop_event = threading.Event()
def start_background_watcher():
"""启动非阻塞的文件监听线程"""
observer_thread = threading.Thread(
target=file_watcher,
args=(stop_event,), # 传入 stop_event 实现可控退出
daemon=True,
name="FileWatcherThread"
)
observer_thread.start()
return observer_thread
# 启动监听线程(在 Flask 运行前)
watcher_thread = start_background_watcher()
# 注册应用退出时的清理动作
def cleanup_watcher():
if not stop_event.is_set():
stop_event.set() # 通知监听线程退出
watcher_thread.join(timeout=2.0) # 最多等待 2 秒
print("✅ File watcher stopped gracefully.")
atexit.register(cleanup_watcher)
if __name__ == '__main__':
port = core.settings.get('listening_port', 5000)
try:
app.run(
debug=True,
host="0.0.0.0",
port=port
)
except KeyboardInterrupt:
print("\n⚠️ Flask server interrupted. Shutting down...")
finally:
cleanup_watcher() # 确保清理执行
2. 改造 flask_commands.py:支持外部控制退出
原 file_watcher() 函数依赖 KeyboardInterrupt,不适用于线程环境。需重构为响应 threading.Event:
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
import time
import logging
from watchdog.observers import Observer
from your_package.file_watcher import Handler
from your_package import core
@click.command()
def file_watcher(stop_event: threading.Event = None) -> None:
"""
文件监听命令(线程安全版)
:param stop_event: 外部传入的事件对象,用于触发优雅退出
"""
context.interactor_type = 'flask command'
event_handler = Handler()
folder_path = core.settings['folder_path']
observer = Observer()
observer.schedule(event_handler, folder_path, recursive=True)
observer.start()
try:
while not stop_event.wait(1.0): # 每秒检查一次退出信号
pass
except Exception as e:
logging.error(f"Watcher error: {e}")
finally:
observer.stop()
observer.join(timeout=1.0)
logging.info("File watcher observer stopped.")
3. (可选)增强健壮性:使用 teardown_appcontext
若需更紧密绑定 Flask 应用上下文生命周期(尤其在测试或 WSGI 部署场景),可改用:
@app.teardown_appcontext
def shutdown_watcher(exception):
if exception is None and not stop_event.is_set():
stop_event.set()
watcher_thread.join(timeout=1.5)
⚠️ 注意事项:
- 勿调用 thread.run():原文中 file_watcher_thread.run() 是错误写法,会同步执行而非启动新线程;应使用 .start()。
- 避免 daemon=False:非守护线程会阻止主进程退出,导致 Ctrl+C 后进程残留。
- Windows 兼容性:signal 方案在 Windows 上受限,统一使用 threading.Event 更可靠。
- 生产部署建议:对于高可用场景,推荐将后台任务拆分为独立服务(如 Celery Beat 或 systemd service),而非嵌入 Flask 进程。
通过以上改造,你的文件监听任务将作为轻量级守护线程与 Flask 服务共存,启动即运行、退出即释放,兼顾开发便捷性与运行稳定性。










