我正在编写一个可以做一些繁重工作的Web应用程序。考虑到这一点,我想到将任务作为后台任务(非阻塞),以便其他请求不会被之前的任务阻止。
我开始妖魔化线程,以便一旦主线程(因为我使用threaded=True
)完成就不会退出,现在如果用户发送请求,我的代码将立即告诉他们他们的请求正在进行中(它将在后台运行),应用程序已准备好提供其他请求。
我当前的应用程序代码看起来像这样。
from flask import Flask
from flask import request
import threading
class threadClass:
def __init__(self):
thread = threading.Thread(target=self.run, args=())
thread.daemon = True # Daemonize thread
thread.start() # Start the execution
def run(self):
#
# This might take several minutes to complete
someHeavyFunction()
app = Flask(__name__)
@app.route('/start', methods=['POST'])
try:
begin = threadClass()
except:
abort(500)
return "Task is in progress"
def main():
"""
Main entry point into program execution
PARAMETERS: none
"""
app.run(host='0.0.0.0',threaded=True)
main()
我只是希望它能够处理一些并发请求(它不会在生产中使用)
我能做得更好吗?我错过了什么吗?我正在浏览python的多线程包并找到了这个
multiprocessing是一个使用a支持产生进程的包 API类似于线程模块。多处理包 提供本地和远程并发,有效地侧面步进 全局解释器锁通过使用子进程而不是线程。 因此,多处理模块允许程序员完全 利用给定计算机上的多个处理器。它可以在Unix上运行 和Windows。
我可以使用多处理妖魔化进程吗?我怎样才能比使用线程模块更好?
好吧所以我经历了python的多处理包,它类似于线程化。
from flask import Flask
from flask import request
from multiprocessing import Process
class processClass:
def __init__(self):
p = Process(target=self.run, args=())
p.daemon = True # Daemonize it
p.start() # Start the execution
def run(self):
#
# This might take several minutes to complete
someHeavyFunction()
app = Flask(__name__)
@app.route('/start', methods=['POST'])
try:
begin = processClass()
except:
abort(500)
return "Task is in progress"
def main():
"""
Main entry point into program execution
PARAMETERS: none
"""
app.run(host='0.0.0.0',threaded=True)
main()
上述方法看起来不错吗?
答案 0 :(得分:1)
在烧瓶中实现后台任务的最佳方法是使用芹菜,如this SO post中所述。一个不错的起点是官方的Flask documentation和Celery documentation。
正如@MrLeeh在评论中指出的那样,Miguel Grinberg在他的Pycon 2016 talk中通过实现装饰器提出了一种解决方案。我想强调,我对他的解决方案表示最高的敬意;他本人称其为“疯狂的解决方案”。以下代码是his solution的较小改编。
请勿在生产环境中使用此功能!。主要原因是此应用通过使用全局tasks
字典出现了内存泄漏。即使您解决了内存泄漏问题,维护此类代码也很困难。如果您只是想在私人项目中玩转或使用它,请继续阅读。
假设您在/foo
端点中有一个长期运行的函数调用。我用10秒的sleep
计时器来模拟。如果您三次调用enpoint,则需要30秒才能完成。
Miguel Grinbergs装饰器解决方案在flask_async
中实现。它在Flask上下文中运行与当前Flask上下文相同的新线程。每个线程都会获得一个新的task_id
。结果保存在全局字典tasks[task_id]['result']
中。
使用装饰器后,您只需用@flask_async
装饰端点,并且端点是异步的-就是这样!
import threading
import time
import uuid
from functools import wraps
from flask import Flask, current_app, request, abort
from werkzeug.exceptions import HTTPException, InternalServerError
app = Flask(__name__)
tasks = {}
def flask_async(f):
"""
This decorator transforms a sync route to asynchronous by running it in a background thread.
"""
@wraps(f)
def wrapped(*args, **kwargs):
def task(app, environ):
# Create a request context similar to that of the original request
with app.request_context(environ):
try:
# Run the route function and record the response
tasks[task_id]['result'] = f(*args, **kwargs)
except HTTPException as e:
tasks[task_id]['result'] = current_app.handle_http_exception(e)
except Exception as e:
# The function raised an exception, so we set a 500 error
tasks[task_id]['result'] = InternalServerError()
if current_app.debug:
# We want to find out if something happened so reraise
raise
# Assign an id to the asynchronous task
task_id = uuid.uuid4().hex
# Record the task, and then launch it
tasks[task_id] = {'task': threading.Thread(
target=task, args=(current_app._get_current_object(), request.environ))}
tasks[task_id]['task'].start()
# Return a 202 response, with an id that the client can use to obtain task status
return {'TaskId': task_id}, 202
return wrapped
@app.route('/foo')
@flask_async
def foo():
time.sleep(10)
return {'Result': True}
@app.route('/foo/<task_id>', methods=['GET'])
def foo_results(task_id):
"""
Return results of asynchronous task.
If this request returns a 202 status code, it means that task hasn't finished yet.
"""
task = tasks.get(task_id)
if task is None:
abort(404)
if 'result' not in task:
return {'TaskID': task_id}, 202
return task['result']
if __name__ == '__main__':
app.run(debug=True)
但是,您需要一些技巧才能获得结果。端点/foo
仅返回HTTP代码202和任务ID,而不返回结果。您需要另一个端点/foo/<task_id>
才能获得结果。这是本地主机的示例:
import time
import requests
task_ids = [requests.get('http://127.0.0.1:5000/foo').json().get('TaskId')
for _ in range(2)]
time.sleep(11)
results = [requests.get(f'http://127.0.0.1:5000/foo/{task_id}').json()
for task_id in task_ids]
# [{'Result': True}, {'Result': True}]