Keeping a process running while the user is able to select other options from multiselect box or click on buttons to download data

Hey

I’m trying to make a streamlit app that contains a process that is being run in Azure VM. This process runs just fine when I click a button with “Run process” and I can see that the VM is being used to do the tasks I want. I then have other options on the side bar that allow me to display HTML reports and download files. The thing is that these are multiselect box and buttons and so every time I click on any of these, the app re-runs and the process that was originally being run in azure gets lost.

Is there any way to overcome this issue with streamlit? Essentially, having multiple things running in parallel where one can be kept running for 1-2 hours while other things are being displayed on the same page?

For things that take a long process you can run them in a separate thread and in a function with independent rerun using fragment so that it will not affect things running in the main thread.

Here is an example, there are two functions simple_chart() and analysis(). You can run analysis on its own thread to do its work. While it is running, you can interact with the chart.

def main():
    # Run in main thread.
    simple_chart()

    # Run in a separate thread.
    analysis()

In this example, the analysis() is this:

@st.experimental_fragment
def analysis():
    with st.container(border=True):
        st.markdown('The process will be run from a different thread and reruns will be executed within the function only.')

        num_jobs = st.number_input('Number of jobs', value=8, min_value=5, max_value=100, step=1)

        st.button('start process', on_click=process_cb, disabled=ss.is_btn_disabled, type='primary')

        if ss.is_btn_disabled:
            wt = threading.Thread(target=work_process, args=(num_jobs,), daemon=True)
            add_script_run_ctx(wt)
            wt.start()
            wt.join()

            # Rerun to redraw the button as enabled.
            st.rerun()

The work_process function is run in a different thread.

def work_process(njobs):
    done = 0

    while done < njobs:
        time.sleep(2)
        done += 1

    ss.is_btn_disabled = False  # enable button

The process button is disabled after starting it and restored once done.

Complete code

import streamlit as st
from streamlit import session_state as ss
import numpy as np
import threading
from streamlit.runtime.scriptrunner import add_script_run_ctx
import time


if 'is_btn_disabled' not in ss:
    ss.is_btn_disabled = False


def simple_chart():
    color_dict = {
        'midnightblue': 'rgb(25, 25, 112)',
        'indigo': 'rgb(75, 0, 130)',
        'red': 'rgb(255, 0, 0)',
        'slategray': 'rgb(112, 128, 144)'
    }
    with st.container(border=True):
        st.markdown('This will be run from the main thread.')
        selected_color = st.selectbox('Bar Color', options=list(color_dict.keys()))
        val = st.slider("Number of bars", 1, 20, 4)
        st.bar_chart(np.random.default_rng().random(val), height=200, color=color_dict[selected_color])


def process_cb():
    ss.is_btn_disabled = True  # disable button


def work_process(njobs):
    done = 0

    while done < njobs:
        time.sleep(2)
        done += 1

    ss.is_btn_disabled = False  # enable button


@st.experimental_fragment
def analysis():
    with st.container(border=True):
        st.markdown('The process will be run from a different thread and reruns will be executed within the function only.')

        num_jobs = st.number_input('Number of jobs', value=8, min_value=5, max_value=100, step=1)

        st.button('start process', on_click=process_cb, disabled=ss.is_btn_disabled, type='primary')

        if ss.is_btn_disabled:
            wt = threading.Thread(target=work_process, args=(num_jobs,), daemon=True)
            add_script_run_ctx(wt)
            wt.start()
            wt.join()

            # Rerun to redraw the button as enabled.
            st.rerun()


def main():
    # Run in main thread.
    simple_chart()

    # Run in a separate thread.
    analysis()


if __name__ == '__main__':
    main()

You need to use streamlit 1.33

3 Likes

Hey, thank you this seems to work quite nice! However, my process will run for 2-3 hours + and I have a drop down menu in streamlit that would allow the user to select different reports to view using streamlit components. However, whenever the process is running, and I select another report to view on streamlit, it doesn’t load. It only loads when the long process ends running

1 Like

Could you create a minimal sample code where the issue is reproducible? Let us try to solve it.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.