What is the best method to have an asyncio event loop run in a Flask app?
My main.py looks like this:
if __name__ == '__main__':
try:
app.run(host='0.0.0.0', port=8000, debug=True)
except:
logging.critical('server: CRASHED: Got exception on main handler')
logging.critical(traceback.format_exc())
raise
To add the option of async tasks, I needed to create an event_loop
before running the app
, but even when stopping the app run, a background thread still hangs (observable in debugger)
if __name__ == '__main__':
try:
app.event_loop = asyncio.get_event_loop()
app.run(host='0.0.0.0', port=8000, debug=True)
except:
logging.critical('server: CRASHED: Got exception on main handler')
logging.critical(traceback.format_exc())
raise
finally:
app.event_loop.stop()
app.event_loop.run_until_complete(app.event_loop.shutdown_asyncgens())
app.event_loop.close()
And using the following to create async tasks:
def future_callback(fut):
if fut.exception():
logging.error(fut.exception())
def fire_and_forget(func, *args, **kwargs):
if callable(func):
future = app.event_loop.run_in_executor(None, func, *args, **kwargs)
future.add_done_callback(future_callback)
else:
raise TypeError('Task must be a callable')
The only solution I could find was to add exit()
at the end of the finally
block, but I don't think its the correct solution.
In order for the asyncio event loop to properly run in Flask 1.x, the Flask application must be run using threads (default worker type for Gunicorn, uWSGI, and the Flask development server): Each thread will run an instance of the Flask application when a request is processed.
Event Loop¶. The event loop is the core of every asyncio application. Event loops run asynchronous tasks and callbacks, perform network IO operations, and run subprocesses. Application developers should typically use the high-level asyncio functions, such as asyncio.run(), and should rarely need to reference the loop object or call its methods.
Performance ¶ Async functions require an event loop to run. Flask, as a WSGI application, uses one worker to handle one request/response cycle. When a request comes in to an async view, Flask will start an event loop in a thread, run the view function there, then return the result.
asyncio. get_running_loop () ¶ Return the running event loop in the current OS thread. If there is no running event loop a RuntimeError is raised. This function can only be called from a coroutine or a callback.
I initially took the approach of 1 event_loop for the entire flask app, but didn't like that for a few different reasons.
Instead, I created a helper file, custom_flask_async.py
with 2 functions in,
import asyncio
def get_set_event_loop():
try:
return asyncio.get_event_loop()
except RuntimeError as e:
if e.args[0].startswith('There is no current event loop'):
asyncio.set_event_loop(asyncio.new_event_loop())
return asyncio.get_event_loop()
raise e
def run_until_complete(tasks):
return get_set_event_loop().run_until_complete(asyncio.gather(*tasks))
I only check for and get_set
the event_loop if it's going to be used, and waited upon in a particular request. And because the main way I'm using this is with a run_until_complete
with a gather, I've that in the helper too.
I'm also not sure if this is the best approach, or what drawbacks there are to this method, but it definitely works for my purpose.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With