The code below uses a background callback (plotly/dash) to retrieve a value.
Every 3 seconds, update_event(event, shared_value) sets an event and define a value. In parallel, listening_process(set_progress) waits for the event.
This seems to work nicely when the waiting time is low (say a few seconds). When the waiting time is below 1 second (say 500ms), listening_process(set_progress) is missing values.
Is there a way for the background callback to refresh faster ?
It looks like the server updates with a delay of a few hundreds of ms, independently of the rate at which I set the event.
import time
from uuid import uuid4
import diskcache
import dash_bootstrap_components as dbc
from dash import Dash, html, DiskcacheManager, Input, Output
from multiprocessing import Event, Value, Process
from datetime import datetime
import random
# Background callbacks require a cache manager + a unique identifier
launch_uid = uuid4()
cache = diskcache.Cache("./cache")
background_callback_manager = DiskcacheManager(
cache, cache_by=[lambda: launch_uid], expire=60,
)
# Creating an event
event = Event()
# Creating a shared value
shared_value = Value('i', 0) # 'i' denotes an integer type
# Updating the event
# This will run in a different process using multiprocessing
def update_event(event, shared_value):
while True:
event.set()
with shared_value.get_lock(): # ensure safe access to shared value
shared_value.value = random.randint(1, 100) # generate a random integer between 1 and 100
print("Updating event...", datetime.now().time(), "Shared value:", shared_value.value)
time.sleep(3)
app = Dash(__name__, background_callback_manager=background_callback_manager)
app.layout = html.Div([
html.Button('Run Process', id='run-button'),
dbc.Row(children=[
dbc.Col(
children=[
# Component sets up the string with % progress
html.P(None, id="progress-component")
]
),
]
),
html.Div(id='output')
])
# Listening for the event and generating a random process
def listening_process(set_progress):
while True:
event.wait()
event.clear()
print("Receiving event...", datetime.now().time())
with shared_value.get_lock(): # ensure safe access to shared value
value = shared_value.value # read the shared value
set_progress(value)
@app.callback(
[
Output('run-button', 'style', {'display': 'none'}),
Output("progress-component", "children"),
],
Input('run-button', 'n_clicks'),
prevent_initial_call=True,
background=True,
running=[
(Output("run-button", "disabled"), True, False)],
progress=[
Output("progress-component", "children"),
],
)
def run_process(set_progress, n_clicks):
if n_clicks is None:
return False, None
elif n_clicks > 0:
p = Process(target=update_event, args=(event,shared_value))
p.start()
listening_process(set_progress)
return True, None
if __name__ == '__main__':
app.run(debug=True, port=8050)
EDIT : found a solution using interval.
@app.callback(
[
Output('run-button', 'style', {'display': 'none'}),
Output("progress-component", "children"),
],
Input('run-button', 'n_clicks'),
prevent_initial_call=True,
interval= 100, #### **THIS IS NEW**
background=True,
running=[
(Output("run-button", "disabled"), True, False)],
progress=[
Output("progress-component", "children"),
],
)
But then, what is the advantage of this approach over a dcc.interval ? In both cases, there is polling. The only advantage I can see is that the consumer receives the updated value at the exact same time it is produced.
