Async and Concurrency (for dummies)

Hey,

For those of you who use and love Panel but are no professionals like myself here’s a little help for Async and Concurrency. It’s a “bit more” straightforward than the official user guide’s description.

Please, see code snippet and link below for details. Tried and tested on Heroku.

@unsync
async def unsync_async():
   await asyncio.sleep(0.1)
   return 'I like decorators'

unsync_async().result()

Unsync

To give you an idea…
Declare a widget with placeholder info for initial load then in the background run and async function to update its value/object/etc.

Hi,
That looks interesting.
If I understand this correctly, everything still happens in the same thread right? So there is no need for pn.io.unlocked() ? Does this keep the GUI responsive while the task is being executed?

Also, perhaps a long shot, any way of making this work with dask?
For what I’d like to do see this post here: Panel webapp with dask/bokeh/tornado and asynchronous tasks - #5 by Jhsmit

Is this equivalent to pn.state.onload(load_data) Defer data load — Panel 0.11.3 documentation

@Jhsmit @ahuang11
What I can say is that the GUI does stay responsive. I can’t confirm your other questions regarding threads/Dask/etc.

An example I can give you. A Panel app running on Heroku took about 30 seconds to load up and this was not due to dyno sleep/boot time. During the 30 seconds data got downloaded and processed from database to be turned into lists for select widgets, datepicker widgets, etc.

Once I started using the above method, the app load time dropped below 5 seconds and was responsive before the widgets’ objects/values got updated, if that makes sense.

For me the main advantage of this method is its readability. Apart from speed of course…

Also, for those of you not familiar with unsync… when you are calling your function, you don’t have to use .result()

Further to this, check out Unsync’s GitHub page to have a better understanding of how you can use the @unsync decorator with normal functions or with async functions, how you can mix them, how you can chain them. How .result() works. What these mean in terms of threading, etc.

Unsync - GitHub

…and all the above seems to be working with Panel.

I’ll have a look, my last tangle with Dask i had issues because the dask future’s are different from asyncio features. But perhaps this library can help.

What I’m using now with the period callback seems to incur high CPU usage on client PC’s…

Hi @danmaty

Is it possible for you to share a larger example with the community of using unsync? It would be really interesting.

It could be as a showcase on Discourse, A blog post, A Gallery Notebook or an addition to the existing documentation.

Just an idea. We all need help creating high performing apps.

Hi @Marc
I can try to put something together. Happy to share.

1 Like

Hi @Marc

Have a look at the below and let me know if you think it’s too much or too little. Just a simple demo with three Unsync wrapped functions with non-blocking sleep calls manipulating panel objects.

If this is something you had in mind or not far off I can write a few sentences or something to give it some shape/form.

import panel as pn
import param
from unsync import unsync
from time import sleep as sl
from time import strftime as st
from panel.layout.gridstack import GridStack

CSS = """
#header {
    background-color: rgba(255,255,255,0.33);
    -ms-box-shadow: none !important;
    -o-box-shadow: none !important;
    -moz-box-shadow: none !important;
    -webkit-box-shadow: none !important;
    box-shadow: none !important;
}

.main-content {
    transition: all 0.2s cubic-bezier(0.945, 0.020, 0.270, 0.665);
    width: 100%;
    height: calc(100vh - 76px);
    padding: 10px;
}

body {
    background: radial-gradient(#B8BEB4, #71685F);
}
 
.bk-root button.button {
    width:65px;
    height:55px;
    position:absolute;
    bottom:0px;
    background-color: rgba(255,255,255,0.33);
    color:#FFF;
    border-radius:25px;
    text-align:center;
    box-shadow: 2px 2px 3px black;
    transition: all 0.2s ease-in-out;
    font-size:30px;
    border-color: rgba(255,255,255,0.33);
}

.bk-root button.button:hover {
    box-shadow: 4px 4px 3px black;
    background-color: rgba(255,255,255,0.33);
    transform: scale(1.05);
    border-color: rgba(255,255,255,0.33);
    cursor: pointer;
}

.bk-root button.button:active {
    transform: translateY(1px);
}
"""

pn.config.sizing_mode = 'stretch_both'
pn.extension('gridstack', raw_css=[CSS])

playSVG="""<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 20 20" height="48px" viewBox="0 0 20 20" width="48px" fill="#000000"><g><rect fill="none" height="20" width="20"/></g><g><g><path d="M10,2c-4.42,0-8,3.58-8,8s3.58,8,8,8s8-3.58,8-8S14.42,2,10,2z M10,16.5c-3.58,0-6.5-2.92-6.5-6.5S6.42,3.5,10,3.5 s6.5,2.92,6.5,6.5S13.58,16.5,10,16.5z"/><polygon points="8,13.5 13.5,10 8,6.5"/></g></g></svg>
"""
pauseSVG="""<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 0 24 24" width="48px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>
"""
stopSVG="""<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 0 24 24" width="48px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M16 8v8H8V8h8m2-2H6v12h12V6z"/></svg>
"""

@unsync
def unsync_func1(name):
    global stop_unsync1
    print('unsync1', name)
    if name == 'play':
        stop_unsync1 = False
        while 1:
            if stop_unsync1 == True:
                break
            time_unsync1 = st("%H:%M:%S")
            unsync_out1.object = f"<h1>{time_unsync1}</h1>"
            sl(0.1)
            # print('unsync1 running...')
    elif name == 'pause':
        stop_unsync1 = True
    elif name == 'stop':
        stop_unsync1 = True
        unsync_out1.object = "<h1>00:00:00</h1>"

@unsync
def unsync_func2(name):
    global stop_unsync2
    print('unsync2', name)
    if name == 'play':
        stop_unsync2 = False
        while 1:
            if stop_unsync2 == True:
                break
            time_unsync2 = st("%H:%M:%S")
            unsync_out2.object = f"<h1>{time_unsync2}</h1>"
            sl(0.1)
            # print('unsync2 running...')
    elif name == 'pause':
        stop_unsync2 = True
    elif name == 'stop':
        stop_unsync2 = True
        unsync_out2.object = "<h1>00:00:00</h1>"

@unsync
def unsync_func3(name):
    global stop_unsync3
    print('unsync3', name)
    if name == 'play':
        stop_unsync3 = False
        while 1:
            if stop_unsync3 == True:
                break
            time_unsync3 = st("%H:%M:%S")
            unsync_out3.object = f"<h1>{time_unsync3}</h1>"
            sl(0.1)
            # print('unsync3 running...')
    elif name == 'pause':
        stop_unsync3 = True
    elif name == 'stop':
        stop_unsync3 = True
        unsync_out3.object = "<h1>00:00:00</h1>"

class SVGButton(pn.reactive.ReactiveHTML):
    svg = param.String(doc="The SVG")
    name = param.String(doc='Icon ID')
    func_type = param.String(doc='Type of function')

    _template = """<button class="button" type="button" id="button" onclick="${_do}">{{svg}}</button>"""

    def _do(self, _):
        if self.func_type == 'unsync1':
            unsync_func1(self.name)
        elif self.func_type == 'unsync2':
            unsync_func2(self.name)
        elif self.func_type == 'unsync3':
            unsync_func3(self.name)

play_button_unsync1 = SVGButton(svg=playSVG, name='play', func_type='unsync1', height=60, width=60)
pause_button_unsync1 = SVGButton(svg=pauseSVG, name='pause', func_type='unsync1', height=60, width=60)
stop_button_unsync1 = SVGButton(svg=stopSVG, name='stop', func_type='unsync1', height=60, width=60)
unsync_buttons1 = pn.Row(play_button_unsync1, pause_button_unsync1, stop_button_unsync1)

play_button_unsync2 = SVGButton(svg=playSVG, name='play', func_type='unsync2', height=60, width=60)
pause_button_unsync2 = SVGButton(svg=pauseSVG, name='pause', func_type='unsync2', height=60, width=60)
stop_button_unsync2 = SVGButton(svg=stopSVG, name='stop', func_type='unsync2', height=60, width=60)
unsync_buttons2 = pn.Row(play_button_unsync2, pause_button_unsync2, stop_button_unsync2)

play_button_unsync3 = SVGButton(svg=playSVG, name='play', func_type='unsync3', height=60, width=60)
pause_button_unsync3 = SVGButton(svg=pauseSVG, name='pause', func_type='unsync3', height=60, width=60)
stop_button_unsync3 = SVGButton(svg=stopSVG, name='stop', func_type='unsync3', height=60, width=60)
unsync_buttons3 = pn.Row(play_button_unsync3, pause_button_unsync3, stop_button_unsync3)

unsync_out1 = pn.pane.HTML(object="""<h1>00:00:00</h1>""")
unsync_out2 = pn.pane.HTML(object="""<h1>00:00:00</h1>""")
unsync_out3 = pn.pane.HTML(object="""<h1>00:00:00</h1>""")

card_bg='rgba(255,255,255,0.33)'

gs = GridStack(sizing_mode='stretch_both', ncols=5, nrows=3, height=500, allow_resize=False, allow_drag=False)

gs[0:1, 0:5] = pn.Spacer(margin=5)
gs[1:2, 1:2] = pn.Card(unsync_out1, title='Unsync Function 1', background=card_bg, collapsible=False)
gs[1:2, 2:3] = pn.Card(unsync_out2, title='Unsync Function 2', background=card_bg, collapsible=False)
gs[1:2, 3:4] = pn.Card(unsync_out3, title='Unsync Function 3', background=card_bg, collapsible=False)
gs[2:3, 1:2] = unsync_buttons1
gs[2:3, 2:3] = unsync_buttons2
gs[2:3, 3:4] = unsync_buttons3

mt = pn.template.MaterialTemplate(
    header_background='rgba(255,255,255,0.33)',
    title='Unsync Demo',
    main=[gs],
).show()

Thanks @danmaty. I moved the discussion here Provide example of asynchronous and concurrent tasks using unsync · Issue #2792 · holoviz/panel (github.com) to start turning this into a Panel gallery example or some Panel documentation.