Flask-Executor 是 Flask 生态中一个轻量级但功能强大的异步任务处理扩展。它基于 Python 标准库中的 concurrent.futures 模块构建,为 Flask 应用提供了开箱即用的异步任务处理能力。
在传统同步模型中,当 Flask 应用收到一个请求时,会按顺序执行以下步骤:
这种模式在处理耗时操作(如发送邮件、文件处理等)时会导致严重的性能问题。例如,一个需要 3 秒完成的邮件发送操作会阻塞整个请求处理流程,在此期间服务器无法响应其他请求。
异步模型则采用"任务委派"机制:
这种模式使得 Web 服务器能够保持高响应性,特别适合 I/O 密集型操作。Flask-Executor 正是为 Flask 应用提供了这种异步处理能力。
Flask-Executor 的核心是 concurrent.futures 中的 ThreadPoolExecutor 和 ProcessPoolExecutor。在初始化时,它会创建一个线程池或进程池(默认为 CPU 核心数×2 的线程池),所有提交的任务都会被放入这个池中排队执行。
关键组件交互流程:
这种架构保证了任务执行的线程安全,同时避免了开发者直接操作线程带来的复杂性。
Flask-Executor 支持两种执行器类型,需根据任务特性合理选择:
python复制# I/O 密集型任务(网络请求、文件操作等)
app.config['EXECUTOR_TYPE'] = 'thread' # 默认值
# CPU 密集型任务(图像处理、数值计算等)
app.config['EXECUTOR_TYPE'] = 'process'
线程执行器特点:
进程执行器特点:
python复制app.config.update(
EXECUTOR_MAX_WORKERS=10, # 工作线程/进程数
EXECUTOR_PROPAGATE_EXCEPTIONS=True, # 是否传播异常
EXECUTOR_FS_KIND='tempfile' # 进程间通信文件存储方式
)
工作线程数设置经验:
提示:过度增加工作线程数会导致线程竞争和上下文切换开销,反而降低性能。建议通过压力测试找到最佳值。
python复制from flask_executor import Executor
from concurrent.futures import ThreadPoolExecutor
# 自定义线程池实现
class CustomThreadPoolExecutor(ThreadPoolExecutor):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._task_log = []
def submit(self, fn, *args, **kwargs):
task_id = f"task_{len(self._task_log)}"
self._task_log.append({
'id': task_id,
'start_time': time.time(),
'status': 'pending'
})
future = super().submit(fn, *args, **kwargs)
future.task_id = task_id
return future
# 使用自定义执行器
executor = Executor(app)
executor.executor_class = CustomThreadPoolExecutor
这种扩展方式可以实现任务监控、自定义日志等高级功能。
任务提交模式对比:
python复制# 基本提交(不关心结果)
future = executor.submit(task_func, arg1, arg2)
# 带回调的提交
def callback(future):
try:
result = future.result()
logger.info(f"Task completed: {result}")
except Exception as e:
logger.error(f"Task failed: {e}")
future = executor.submit(task_func, arg1, arg2)
future.add_done_callback(callback)
# 批量提交
futures = [executor.submit(task_func, i) for i in range(10)]
results = [f.result() for f in futures] # 阻塞等待所有完成
任务状态检查方法:
python复制future.running() # 是否正在执行
future.done() # 是否完成(成功或失败)
future.cancelled() # 是否被取消
future.result(timeout=5) # 获取结果(可设置超时)
future.exception() # 获取异常对象
Flask 的上下文是线程局部的,在后台任务中直接访问 current_app 等会报错。解决方案:
python复制# 方法1:显式传递app对象
def background_task(app, data):
with app.app_context():
# 这里可以安全使用current_app等
db.session.add(data)
db.session.commit()
# 方法2:使用executor的上下文包装
@executor.job
def background_task(data):
# 自动包含应用上下文
current_app.logger.info("Processing data")
return process_data(data)
健壮的任务模板:
python复制def robust_task(max_retries=3, *args, **kwargs):
for attempt in range(max_retries):
try:
result = perform_task(*args, **kwargs)
return {
'status': 'success',
'result': result,
'attempts': attempt + 1
}
except TemporaryError as e:
wait_time = 2 ** attempt # 指数退避
time.sleep(wait_time)
continue
except PermanentError as e:
return {
'status': 'failed',
'error': str(e),
'attempts': attempt + 1
}
return {
'status': 'failed',
'error': 'Max retries exceeded',
'attempts': max_retries
}
监控指标:
优化策略:
python复制@app.route('/adjust-pool/<int:size>')
def adjust_pool(size):
executor.max_workers = size
return f"Thread pool adjusted to {size} workers"
python复制from queue import PriorityQueue
class PriorityThreadPoolExecutor(ThreadPoolExecutor):
def __init__(self, max_workers=None):
super().__init__(max_workers)
self._work_queue = PriorityQueue()
python复制import newrelic.agent
def instrumented_task(*args, **kwargs):
@newrelic.agent.background_task()
def _task():
# 实际任务逻辑
return perform_real_work(*args, **kwargs)
return _task()
python复制@app.route('/process-document', methods=['POST'])
def process_document():
if 'file' not in request.files:
return "No file uploaded", 400
file = request.files['file']
if file.filename == '':
return "No selected file", 400
# 生成唯一任务ID
task_id = str(uuid.uuid4())
# 存储文件内容(生产环境应使用对象存储)
file_content = file.read()
file_storage[task_id] = file_content
# 提交处理任务
future = executor.submit(
process_document_task,
task_id,
file.filename,
file_content
)
# 存储Future对象
task_futures[task_id] = future
return jsonify({
'task_id': task_id,
'status': 'queued',
'filename': file.filename
})
def process_document_task(task_id, filename, content):
try:
# 模拟耗时处理
time.sleep(3)
# 处理逻辑
result = {
'size': len(content),
'type': filename.split('.')[-1],
'analysis': perform_analysis(content)
}
# 存储结果
task_results[task_id] = {
'status': 'completed',
'result': result,
'completed_at': datetime.now().isoformat()
}
except Exception as e:
task_results[task_id] = {
'status': 'failed',
'error': str(e),
'failed_at': datetime.now().isoformat()
}
python复制def send_email_async(to, subject, template, context):
"""异步发送邮件"""
# 渲染模板
msg_body = render_template(template, **context)
# 使用Flask-Mail扩展
msg = Message(
subject,
recipients=[to],
body=msg_body
)
try:
mail.send(msg)
return {'status': 'sent', 'to': to}
except Exception as e:
current_app.logger.error(f"邮件发送失败: {e}")
raise
@app.route('/send-notification', methods=['POST'])
def send_notification():
data = request.json
executor.submit(
send_email_async,
to=data['email'],
subject="您的通知",
template='email/notification.txt',
context={'user': data['name']}
)
return jsonify({'status': 'processing'}), 202
python复制from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class AsyncTask(db.Model):
id = db.Column(db.String(36), primary_key=True)
name = db.Column(db.String(128))
status = db.Column(db.String(32)) # pending, running, completed, failed
created_at = db.Column(db.DateTime, default=datetime.utcnow)
started_at = db.Column(db.DateTime)
completed_at = db.Column(db.DateTime)
result = db.Column(db.Text)
error = db.Column(db.Text)
def track_task(task_func):
"""任务追踪装饰器"""
def wrapper(*args, **kwargs):
task_id = str(uuid.uuid4())
task = AsyncTask(
id=task_id,
name=task_func.__name__,
status='running',
started_at=datetime.utcnow()
)
db.session.add(task)
db.session.commit()
try:
result = task_func(*args, **kwargs)
task.status = 'completed'
task.result = str(result)
task.completed_at = datetime.utcnow()
db.session.commit()
return result
except Exception as e:
task.status = 'failed'
task.error = str(e)
db.session.commit()
raise
return wrapper
python复制def execute_with_dependencies(main_task, dependencies):
"""执行带依赖关系的任务"""
# 先执行依赖任务
dep_futures = [executor.submit(dep) for dep in dependencies]
# 等待所有依赖完成
for future in dep_futures:
future.result() # 如果依赖失败会抛出异常
# 执行主任务
return executor.submit(main_task)
@app.route('/pipeline')
def run_pipeline():
future = execute_with_dependencies(
main_task=process_data,
dependencies=[fetch_data, validate_data]
)
return jsonify({'task_id': str(future)})
| 特性 | Flask-Executor | Celery |
|---|---|---|
| 依赖 | 无额外依赖 | 需要消息代理(RabbitMQ/Redis) |
| 学习曲线 | 简单 | 中等 |
| 任务持久化 | 不支持 | 支持 |
| 分布式支持 | 有限 | 完善 |
| 定时任务 | 需额外实现 | 内置支持 |
| 任务重试 | 需手动实现 | 内置支持 |
| 适合场景 | 简单异步任务 | 复杂任务队列 |
| 内存占用 | 较低 | 较高 |
是否需要任务持久化?
是否需要分布式工作节点?
任务执行时间是否超过 5 分钟?
是否希望避免额外基础设施依赖?
长时间运行的应用需要注意:
python复制@app.route('/memory-info')
def memory_info():
import psutil
process = psutil.Process()
return jsonify({
'rss': process.memory_info().rss,
'vms': process.memory_info().vms,
'threads': process.num_threads()
})
python复制import signal
def handle_shutdown(signum, frame):
"""处理优雅关闭"""
print("接收到关闭信号,等待任务完成...")
executor.shutdown(wait=True)
print("所有任务已完成,应用退出")
sys.exit(0)
signal.signal(signal.SIGTERM, handle_shutdown)
signal.signal(signal.SIGINT, handle_shutdown)
python复制def run_with_timeout(task_func, timeout, *args, **kwargs):
"""带超时控制的任务执行"""
future = executor.submit(task_func, *args, **kwargs)
try:
return future.result(timeout=timeout)
except concurrent.futures.TimeoutError:
future.cancel()
raise TimeoutError(f"Task exceeded {timeout} seconds")
@app.route('/time-sensitive-task')
def time_sensitive_task():
try:
result = run_with_timeout(process_data, 5, request.args)
return jsonify(result)
except TimeoutError as e:
return jsonify({'error': str(e)}), 504
Flask-Executor 作为 Flask 生态中的轻量级异步解决方案,在保持简单性的同时提供了足够的灵活性。对于中小型应用的异步需求,它能够显著提升系统吞吐量而不引入额外复杂性。但在任务持久化、分布式执行等高级场景下,建议评估 Celery 等更专业的解决方案。