marimo-team / marimo

A reactive notebook for Python — run reproducible experiments, execute as a script, deploy as an app, and version with git.
https://marimo.io
Apache License 2.0
7.43k stars 257 forks source link

Blocking tasks (sync or async) leads to ui frozen #1305

Closed mrdobalina2k closed 1 month ago

mrdobalina2k commented 6 months ago

Describe the bug

Stacked UI elements are not updated when a task is running, which blocks the main thread. This is the case for both async and sync code. For example, I want to create a spinner that pops up conditionally when I press a button, and place that spinner in a stacked configuration. Instead of showing up, the stacked element greys out. I can see the other element where the spinner is created, that it pops up. For async this is not the case.

This occurs on other elements as well, for instance markdown elements.

Environment

{ "marimo": "0.4.10", "OS": "Windows", "OS Version": "11", "Processor": "Intel64 Family 6 Model 126 Stepping 5, GenuineIntel", "Python Version": "3.12.2", "Binaries": { "Browser": "123.0.6312.123", "Node": "v14.16.0" }, "Requirements": { "click": "8.1.7", "importlib-resources": "missing", "jedi": "0.19.1", "markdown": "3.6", "pymdown-extensions": "10.7.1", "pygments": "2.17.2", "tomlkit": "0.12.4", "uvicorn": "0.29.0", "starlette": "0.37.2", "websocket": "missing", "typing-extensions": "4.9.0", "black": "24.3.0" } }

Code to reproduce

import marimo

__generated_with = "0.4.10"
app = marimo.App()

@app.cell
def __():
    import marimo as mo
    import asyncio
    from time import sleep
    return asyncio, mo, sleep

@app.cell
def __(mo):
    get_do_something_async, set_do_something_async = mo.state(False)
    get_do_something_sync, set_do_something_sync = mo.state(False)
    return (
        get_do_something_async,
        get_do_something_sync,
        set_do_something_async,
        set_do_something_sync,
    )

@app.cell
def __(mo, set_do_something_async):
    button_do_async = mo.ui.button(label="Do something async", on_click=lambda v: set_do_something_async(True))
    return button_do_async,

@app.cell
def __(mo, set_do_something_sync):
    button_do_sync = mo.ui.button(label="Do something sync", on_click=lambda v: set_do_something_sync(True))
    return button_do_sync,

@app.cell
def __(get_do_something_async, mo):
    ui_do_async_spinner = mo.status.spinner(title="loading") if get_do_something_async() else mo.md("Waiting for async button press")
    ui_do_async_spinner
    return ui_do_async_spinner,

@app.cell
def __(get_do_something_sync, mo):
    ui_do_sync_spinner = mo.status.spinner(title="loading") if get_do_something_sync() else mo.md("Waiting for sync button press")
    ui_do_sync_spinner
    return ui_do_sync_spinner,

@app.cell
def __(get_do_something_sync, mo):
    ui_do_sync_pure_markdown = mo.md("""Running""") if get_do_something_sync() else mo.md("Waiting for sync button press")
    ui_do_sync_pure_markdown
    return ui_do_sync_pure_markdown,

@app.cell
async def __(asyncio, get_do_something_async, set_do_something_async):
    _trigger_async = get_do_something_async()

    async def do_something_async():
        await asyncio.sleep(5)
        print("Done")

    if _trigger_async:
        await do_something_async()
        set_do_something_async(False)
    return do_something_async,

@app.cell
def __(get_do_something_sync, set_do_something_sync, sleep):
    _trigger_sync = get_do_something_sync()

    def do_something_sync():
        sleep(5)
        print("Done")

    if _trigger_sync:
        do_something_sync()
        set_do_something_sync(False)
    return do_something_sync,

@app.cell
def __(button_do_sync, mo, ui_do_sync_spinner):
    mo.vstack([button_do_sync, ui_do_sync_spinner])
    return

@app.cell
def __(button_do_async, mo, ui_do_async_spinner):
    mo.vstack([button_do_async, ui_do_async_spinner])
    return

@app.cell
def __(button_do_sync, mo, ui_do_sync_pure_markdown):
    mo.vstack([button_do_sync, ui_do_sync_pure_markdown])
    return

if __name__ == "__main__":
    app.run()
mscolnick commented 6 months ago

I'm not 100% sure i follow the use case - i'll try to digest it more. But in the meantime, have you see the mo.output.replace/mo.output.append/mo.output.clear? The allow you to update the cell output (replace/append/clear) before the cell finished.

e.g.

mo.output.replace(mo.status.spinner(title="loading"))

# do things

mo.ouput.replace(mo.md("First result: ", result)

# do more things

result

https://docs.marimo.io/api/outputs.html#cell-outputs

mrdobalina2k commented 6 months ago

It's related to the example given in https://github.com/marimo-team/marimo/issues/1271, This works when the action (clicking a button), is inexpensive. However, in my use case I am calling a calculation engine, which takes seconds to return the results. Instead of showing the spinner, it doesn't show up and once the output returns it is gone. I'll take a look at the mo.output, maybe that's what I need..

mscolnick commented 1 month ago

@mrdobalina2k is this good to close out? not sure if you are still experiencing issues