Diş çekme başka bir olası çözümdür. Kereviz tabanlı çözüm, ölçekli uygulamalar için daha iyi olsa da, söz konusu uç noktada çok fazla trafik beklemiyorsanız, iş parçacığı uygun bir alternatiftir.
Bu çözüm, Miguel Grinberg'in PyCon 2016 Flask at Scale sunumuna dayanmaktadır , özellikle slayt sunumundaki 41 numaralı slayt. Onun kodu da github geçerli orijinal kaynağa ilgilenen kişiler için.
Kullanıcı açısından bakıldığında kod şu şekilde çalışır:
- Uzun süre çalışan görevi gerçekleştiren uç noktaya bir çağrı yaparsınız.
- Bu uç nokta, görev durumunu kontrol etmek için bir bağlantıyla birlikte 202 Kabul Edildi değerini döndürür.
- Durum bağlantısına yapılan çağrılar, taks hala çalışırken 202'yi, görev tamamlandığında ise 200'ü (ve sonucu) döndürür.
Bir api çağrısını arka plan görevine dönüştürmek için, @async_api dekoratörünü eklemeniz yeterlidir.
İşte tam kapsamlı bir örnek:
from flask import Flask, g, abort, current_app, request, url_for
from werkzeug.exceptions import HTTPException, InternalServerError
from flask_restful import Resource, Api
from datetime import datetime
from functools import wraps
import threading
import time
import uuid
tasks = {}
app = Flask(__name__)
api = Api(app)
@app.before_first_request
def before_first_request():
"""Start a background thread that cleans up old tasks."""
def clean_old_tasks():
"""
This function cleans up old tasks from our in-memory data structure.
"""
global tasks
while True:
five_min_ago = datetime.timestamp(datetime.utcnow()) - 5 * 60
tasks = {task_id: task for task_id, task in tasks.items()
if 'completion_timestamp' not in task or task['completion_timestamp'] > five_min_ago}
time.sleep(60)
if not current_app.config['TESTING']:
thread = threading.Thread(target=clean_old_tasks)
thread.start()
def async_api(wrapped_function):
@wraps(wrapped_function)
def new_function(*args, **kwargs):
def task_call(flask_app, environ):
with flask_app.request_context(environ):
try:
tasks[task_id]['return_value'] = wrapped_function(*args, **kwargs)
except HTTPException as e:
tasks[task_id]['return_value'] = current_app.handle_http_exception(e)
except Exception as e:
tasks[task_id]['return_value'] = InternalServerError()
if current_app.debug:
raise
finally:
tasks[task_id]['completion_timestamp'] = datetime.timestamp(datetime.utcnow())
task_id = uuid.uuid4().hex
tasks[task_id] = {'task_thread': threading.Thread(
target=task_call, args=(current_app._get_current_object(),
request.environ))}
tasks[task_id]['task_thread'].start()
print(url_for('gettaskstatus', task_id=task_id))
return 'accepted', 202, {'Location': url_for('gettaskstatus', task_id=task_id)}
return new_function
class GetTaskStatus(Resource):
def get(self, task_id):
"""
Return status about an asynchronous task. If this request returns a 202
status code, it means that task hasn't finished yet. Else, the response
from the task is returned.
"""
task = tasks.get(task_id)
if task is None:
abort(404)
if 'return_value' not in task:
return '', 202, {'Location': url_for('gettaskstatus', task_id=task_id)}
return task['return_value']
class CatchAll(Resource):
@async_api
def get(self, path=''):
print("starting processing task, path: '%s'" % path)
time.sleep(10)
print("completed processing task, path: '%s'" % path)
return f'The answer is: {path}'
api.add_resource(CatchAll, '/<path:path>', '/')
api.add_resource(GetTaskStatus, '/status/<task_id>')
if __name__ == '__main__':
app.run(debug=True)