Dash callbacks are not async - handling multiple requests and callbacks in parallel

I have two callbacks bound to the same input component (html.Button: n_clicks) and it seems that one blocks the other (I’ve tested using time.sleep). Is this intended? I need one callback to be executed first, but the order seems to be quite random. If it were async, I could use a semaphore. I’m using app.run_server(debug=True) and have tried threaded=True, but that seems to be the default

Yes, this is intended. To enable your dash app to handle multiple callbacks in parallel, run the app using gunicorn like:

# in file app.py
server = app.server
$ gunicorn app:server # app refers to app.py, server refers to the variable

Or run with multiple processes:

app.run_server(debug=True, processes=4)

For production applications, it’s recommended that you use gunicorn. For more, see https://plot.ly/dash/deployment.

1 Like

My understanding is that gunicorn defaults to 1 worker process, so just using it out of the box as you indicate won’t allow your app to handle multiple callbacks in parallel. To specify multiple workers just do:

$ gunicorn -w 2 app:server

In my experience this will break the CSRF protection that Dash automatically adds to Flask, as the different workers are initialised with different tokens. For this to work, you’ll need to either explicitly supply your own CSRF token or as a simple/hacky workaround you can specify that gunicorn forks the Python process after the app is loaded thereby ensuring the workers share the same CSRF token:

$ gunicorn -w 2 --preload app:server

This Dash Issue contains a discussion of all that: https://github.com/plotly/dash/issues/132

Depending in what your callbacks are doing, using asynchronous worker(s) with multiple threads might be useful. There are different types of async workers, here’s how you can use the gevent one:

$ pip install gunicorn gevent
$ gunicorn --worker-class gevent --threads 8

You can also combine to have multiple async worker processes.

3 Likes

Thanks. It looks like all these solutions don’t work on Windows (which I need for other dependencies in my app). gunicorn is Linux only and the processes=4 kwarg requires os.fork which is missing in Windows. Is there a Windows alternative for this (simpler the better)?

I also gave gevent’s WSGI server a try and it just completely freezes up trying to switch greenlets (see code below).

[edit] Fixed the code below to use the flask server but doesn’t run async. Also, tried Twisted which runs fine but callbacks are still single-threaded.
[edit 2] I’ve tried gevent.WSGIServer, Tornado, Twisted, and mtwsgi (with 10 threads). All run the Dash app, but none run callbacks async

Why isn’t it possible to just use threading with Werkzeug? - that seems like it would be by far the simplest solution (and helpful to many, who are just running small scale apps). For some reason, the “threaded=True” parameter in app.run_server doesn’t seem to have an effect.

from gevent import monkey
monkey.patch_all()

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
from gevent.wsgi import WSGIServer

server = flask.Flask(__name__)
app = dash.Dash(__name__, server=server)

app = dash.Dash()

app.layout = html.Div([
    dcc.Input(id='my-id', value='initial value', type="text"),
    html.Div(id='my-div')
])

@app.callback(
    Output(component_id='my-div', component_property='children'),
    [Input(component_id='my-id', component_property='value')]
)
def update_output_div(input_value):
    return 'You\'ve entered "{}"'.format(input_value)

if __name__ == '__main__':
    http_server = WSGIServer((127.0.0.1'', 8050), server)
    http_server.serve_forever()

I’m not sure why this doesn’t work for you. This example works for me on my mac:

import dash
import dash_core_components as dcc
import dash_html_components as html

import time

app = dash.Dash()
server = app.server

app.layout = html.Div([
    dcc.Input(id='input'),
    html.Div(id='output')
])


@app.callback(
    dash.dependencies.Output('output', 'children'),
    [dash.dependencies.Input('input', 'value')])
def update_output(value):
    print('Request')
    time.sleep(20)
    return value


if __name__ == '__main__':
    app.run_server(threaded=True)

From Reddit - Dive into anything, some folks recommend Waitress. I ran the previous example with waitress with:

$ waitress-serve --threads=6 my_app:server

where my_app refers to a file called my_app.py. I haven’t verified this on Windows.

I tried waitress with

    waitress.serve(app.server, threads=4)

but the callbacks are not executed on different cpus. And the single cpu load stays under 100% all the time. I think multithreading is not working correctly. Or is it only working with gunicorn?

The only thing that worked so far is the development server

app.run_server(debug=True, threaded=False, processes=n_cpu)
3 Likes