Is there a way to update plotly/dash faster when using a background callback?

46 Views Asked by At

The code below uses a background callback (plotly/dash) to retrieve a value.

enter image description here

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.

0

There are 0 best solutions below