Hi folks,
In the app I’m currently working on, I want to load and process data via a callback triggered by a file upload. The data at the end is saved in a global variable, which allows me to access the processed data throughout the app.
To avoid blocking the main thread due to the time-consuming processing (during the upload), I tried to move this logic into a background callback.
However, I noticed that anything created inside the background callback seems to be running in a different thread, and once the callback is finished, the data is essentially “lost”.
In my minimal example, the update_progress callback stores data in a global variable called DATASTORAGE. Another callback, get_odds, is supposed to read that variable and return only the odd numbers. But in that second callback, DATASTORAGE is empty.
I found a workaround using dcc.Store to persist the data across callbacks, which works fine.
Still, I’m wondering:
Is there any other way to retain data from a background callback?
Can I somehow share data across threads in Dash?
Thanks in advance!
from dash import Dash, DiskcacheManager, CeleryManager, html, Input, Output, dcc
import os
import dash_bootstrap_components as dbc
import time
DATASTORAGE = []
if 'REDIS_URL' in os.environ:
# Use Redis & Celery if REDIS_URL set as an env variable
from celery import Celery
celery_app = Celery(__name__, broker=os.environ['REDIS_URL'], backend=os.environ['REDIS_URL'])
background_callback_manager = CeleryManager(celery_app)
else:
# Diskcache for non-production apps when developing locally
import diskcache
cache = diskcache.Cache("./cache")
background_callback_manager = DiskcacheManager(cache)
modal = dbc.Modal([
dbc.ModalHeader(
dbc.ModalTitle("Progess..."),
close_button=False
),
dbc.ModalBody(children=[
html.Progress(
id="progress_bar",
value="0",
style={'width': '100%'}
)
]),
dbc.ModalFooter(
dbc.Button(
"Cancel",
id="cancel_button",
className="ms-auto",
n_clicks=0
)
)
],
id="modal",
is_open=False,
backdrop="static",
keyboard=False
)
def create_layout(app):
@app.callback(
output=Output("out", "children"),
inputs=[Input("btn-start", 'n_clicks')],
background=True,
running=[
(Output('modal', 'is_open'), True, False)
],
progress=[
Output("progress_bar", "value"),
Output("progress_bar", "max"),
Output("workingfile", "children")
],
cancel=Input("cancel_button", "n_clicks"),
prevent_initial_call=True
)
def update_progress(set_progress, clicks):
values = list(range(0, 100))
total = 500
val = 1
set_progress((str(val), str(total), html.P("")))
for value in values:
val += total / (len(values) + 1)
set_progress((str(val), str(total), html.P(f"{value}")))
time.sleep(0.01)
DATASTORAGE.append(value)
set_progress((str(total), str(total), html.P("Done")))
set_progress((str(0), str(total), html.P()))
print("Datastorage populated in background callback")
print(DATASTORAGE)
return html.P(f"{DATASTORAGE}")
@app.callback(
Output("odds", "children"),
Input("out", "children")
)
def get_odds(_):
print("Second callback")
odds = [x for x in DATASTORAGE if x %2 != 0]
return html.P(f"{odds}")
return html.Div(children=[
modal,
html.Button("Start process..", id="btn-start"),
html.Div(children=[], id="out"),
html.Div(children=[], id="odds"),
],
style={"padding" : "15px"}
)
def main() -> None:
app = Dash(
prevent_initial_callbacks=True,
suppress_callback_exceptions=True,
background_callback_manager=background_callback_manager
)
app.layout = create_layout(app)
app.run(debug=True, port=2222)
if __name__ == "__main__":
main()