How can I add a background thread to flask?
Question:
I’m busy writing a small game server to try out flask. The game exposes an API via REST to users. It’s easy for users to perform actions and query data, however I’d like to service the "game world"
outside the app.run()
loop to update game entities, etc. Given that Flask is so cleanly implemented, I’d like to see if there’s a Flask way to do this.
Answers:
Your additional threads must be initiated from the same app that is called by the WSGI server.
The example below creates a background timer-thread that executes every 5 seconds and manipulates data structures that are also available to Flask routed functions.
import threading
import atexit
from flask import Flask
POOL_TIME = 5 #Seconds
# variables that are accessible from anywhere
common_data_struct = {}
# lock to control access to variable
data_lock = threading.Lock()
# timer handler
your_timer = threading.Timer(0,lambda x: None,())
def create_app():
app = Flask(__name__)
def interrupt():
global your_timer
your_timer.cancel()
def do_stuff():
global common_data_struct
global your_timer
with data_lock:
pass
# Do your stuff with common_data_struct Here
# Set the next timeout to happen
your_timer = threading.Timer(POOL_TIME, do_stuff, ())
your_timer.start()
def do_stuff_start():
# Do initialisation stuff here
global your_timer
# Create your timer
your_timer = threading.Timer(POOL_TIME, do_stuff, ())
your_timer.start()
# Initiate
do_stuff_start()
# When you kill Flask (SIGTERM), cancels the timer
atexit.register(interrupt)
return app
app = create_app()
Call it from Gunicorn with something like this:
gunicorn -b 0.0.0.0:5000 --log-config log.conf --pid=app.pid myfile:app
Signal termination works best on OS’s other than Windows. Although this creates a new timer after each timeout, the other timers should eventually be garbage-collected.
In addition to using pure threads or the Celery queue (note that flask-celery is no longer required), you could also have a look at flask-apscheduler:
https://github.com/viniciuschiele/flask-apscheduler
A simple example copied from https://github.com/viniciuschiele/flask-apscheduler/blob/master/examples/jobs.py:
from flask import Flask
from flask_apscheduler import APScheduler
class Config(object):
JOBS = [
{
'id': 'job1',
'func': 'jobs:job1',
'args': (1, 2),
'trigger': 'interval',
'seconds': 10
}
]
SCHEDULER_API_ENABLED = True
def job1(a, b):
print(str(a) + ' ' + str(b))
if __name__ == '__main__':
app = Flask(__name__)
app.config.from_object(Config())
scheduler = APScheduler()
# it is also possible to enable the API directly
# scheduler.api_enabled = True
scheduler.init_app(app)
scheduler.start()
app.run()
First, you should use any WebSocket or polling mechanics to notify the frontend part about changes that happened. I use Flask-SocketIO
wrapper, and very happy with async messaging for my tiny apps.
Nest, you can do all logic which you need in a separate thread(s), and notify the frontend via SocketIO
object (Flask holds continuous open connection with every frontend client).
As an example, I just implemented page reload on backend file modifications:
<!doctype html>
<script>
sio = io()
sio.on('reload',(info)=>{
console.log(['sio','reload',info])
document.location.reload()
})
</script>
class App(Web, Module):
def __init__(self, V):
## flask module instance
self.flask = flask
## wrapped application instance
self.app = flask.Flask(self.value)
self.app.config['SECRET_KEY'] = config.SECRET_KEY
## `flask-socketio`
self.sio = SocketIO(self.app)
self.watchfiles()
## inotify reload files after change via `sio(reload)``
def watchfiles(self):
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class Handler(FileSystemEventHandler):
def __init__(self,sio):
super().__init__()
self.sio = sio
def on_modified(self, event):
print([self.on_modified,self,event])
self.sio.emit('reload',[event.src_path,event.event_type,event.is_directory])
self.observer = Observer()
self.observer.schedule(Handler(self.sio),path='static',recursive=True)
self.observer.schedule(Handler(self.sio),path='templates',recursive=True)
self.observer.start()
I’m busy writing a small game server to try out flask. The game exposes an API via REST to users. It’s easy for users to perform actions and query data, however I’d like to service the "game world"
outside the app.run()
loop to update game entities, etc. Given that Flask is so cleanly implemented, I’d like to see if there’s a Flask way to do this.
Your additional threads must be initiated from the same app that is called by the WSGI server.
The example below creates a background timer-thread that executes every 5 seconds and manipulates data structures that are also available to Flask routed functions.
import threading
import atexit
from flask import Flask
POOL_TIME = 5 #Seconds
# variables that are accessible from anywhere
common_data_struct = {}
# lock to control access to variable
data_lock = threading.Lock()
# timer handler
your_timer = threading.Timer(0,lambda x: None,())
def create_app():
app = Flask(__name__)
def interrupt():
global your_timer
your_timer.cancel()
def do_stuff():
global common_data_struct
global your_timer
with data_lock:
pass
# Do your stuff with common_data_struct Here
# Set the next timeout to happen
your_timer = threading.Timer(POOL_TIME, do_stuff, ())
your_timer.start()
def do_stuff_start():
# Do initialisation stuff here
global your_timer
# Create your timer
your_timer = threading.Timer(POOL_TIME, do_stuff, ())
your_timer.start()
# Initiate
do_stuff_start()
# When you kill Flask (SIGTERM), cancels the timer
atexit.register(interrupt)
return app
app = create_app()
Call it from Gunicorn with something like this:
gunicorn -b 0.0.0.0:5000 --log-config log.conf --pid=app.pid myfile:app
Signal termination works best on OS’s other than Windows. Although this creates a new timer after each timeout, the other timers should eventually be garbage-collected.
In addition to using pure threads or the Celery queue (note that flask-celery is no longer required), you could also have a look at flask-apscheduler:
https://github.com/viniciuschiele/flask-apscheduler
A simple example copied from https://github.com/viniciuschiele/flask-apscheduler/blob/master/examples/jobs.py:
from flask import Flask
from flask_apscheduler import APScheduler
class Config(object):
JOBS = [
{
'id': 'job1',
'func': 'jobs:job1',
'args': (1, 2),
'trigger': 'interval',
'seconds': 10
}
]
SCHEDULER_API_ENABLED = True
def job1(a, b):
print(str(a) + ' ' + str(b))
if __name__ == '__main__':
app = Flask(__name__)
app.config.from_object(Config())
scheduler = APScheduler()
# it is also possible to enable the API directly
# scheduler.api_enabled = True
scheduler.init_app(app)
scheduler.start()
app.run()
First, you should use any WebSocket or polling mechanics to notify the frontend part about changes that happened. I use Flask-SocketIO
wrapper, and very happy with async messaging for my tiny apps.
Nest, you can do all logic which you need in a separate thread(s), and notify the frontend via SocketIO
object (Flask holds continuous open connection with every frontend client).
As an example, I just implemented page reload on backend file modifications:
<!doctype html>
<script>
sio = io()
sio.on('reload',(info)=>{
console.log(['sio','reload',info])
document.location.reload()
})
</script>
class App(Web, Module):
def __init__(self, V):
## flask module instance
self.flask = flask
## wrapped application instance
self.app = flask.Flask(self.value)
self.app.config['SECRET_KEY'] = config.SECRET_KEY
## `flask-socketio`
self.sio = SocketIO(self.app)
self.watchfiles()
## inotify reload files after change via `sio(reload)``
def watchfiles(self):
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class Handler(FileSystemEventHandler):
def __init__(self,sio):
super().__init__()
self.sio = sio
def on_modified(self, event):
print([self.on_modified,self,event])
self.sio.emit('reload',[event.src_path,event.event_type,event.is_directory])
self.observer = Observer()
self.observer.schedule(Handler(self.sio),path='static',recursive=True)
self.observer.schedule(Handler(self.sio),path='templates',recursive=True)
self.observer.start()