Why this code is not fully asynchronous

hi
i’m trying to test bokeh for a future project where a document is updated after a asynchronous network service call.In the following example, it is simulated by a asyncio.sleep call.
In the example, the two plots are updated asynchronously after a clicked button, but it seems not. why ?

the code is:

import asyncio
from functools import partial
from random import random

from bokeh.layouts import column, row
from bokeh.models import Button
from bokeh.palettes import RdYlBu3
from bokeh.plotting import figure, curdoc

doc = curdoc()

i = 0

create a callback that adds a number in a random location

async def async_callback(ds, wait):
global i

await asyncio.sleep(wait)

# BEST PRACTICE --- update .data in one step with a new dict                                                               
new_data = dict()
new_data['x'] = ds.data['x'] + [random()*70 + 15]
new_data['y'] = ds.data['y'] + [random()*70 + 15]
new_data['text_color'] = ds.data['text_color'] + [RdYlBu3[i%3]]
new_data['text'] = ds.data['text'] + [str(i)]
ds.data = new_data

i = i + 1

def callback(ds, wait):
print(f’wait {wait}’)
doc.add_next_tick_callback(partial(async_callback, ds, wait))

create a plot and style its properties

p = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)
p.border_fill_color = ‘black’
p.background_fill_color = ‘black’
p.outline_line_color = None
p.grid.grid_line_color = None

add a text renderer to the plot (no data yet)

r = p.text(x=, y=, text=, text_color=, text_font_size=“26px”,
text_baseline=“middle”, text_align=“center”)

add a button widget and configure with the call back

button = Button(label=“Press Me with no wait”)
button.on_click(partial(callback, r.data_source, 0))

create a plot and style its properties

p2 = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)
p2.border_fill_color = ‘black’
p2.background_fill_color = ‘black’
p2.outline_line_color = None
p2.grid.grid_line_color = None

add a text renderer to the plot (no data yet)

r2 = p2.text(x=, y=, text=, text_color=, text_font_size=“26px”,
text_baseline=“middle”, text_align=“center”)

button2 = Button(label=“Press Me with wait 10 second”)
button2.on_click(partial(callback,r2.data_source, 10))

put the button and plot in a layout and add to the document

doc.add_root(row(column(button,p), column(button2,p2)))

Hi @ninousf please edit your post to use code formatting so that the code is intelligible (either with the </> icon on the editing toolbar, or triple backtick ``` fences around the code blocks)

excuse me,

import asyncio
from functools import partial
from random import random

from bokeh.layouts import column, row
from bokeh.models import Button
from bokeh.palettes import RdYlBu3
from bokeh.plotting import figure, curdoc

doc = curdoc()

i = 0

# create a callback that adds a number in a random location                                                                    
async def async_callback(ds, wait):
    global i

    await asyncio.sleep(wait)

    # BEST PRACTICE --- update .data in one step with a new dict                                                               
    new_data = dict()
    new_data['x'] = ds.data['x'] + [random()*70 + 15]
    new_data['y'] = ds.data['y'] + [random()*70 + 15]
    new_data['text_color'] = ds.data['text_color'] + [RdYlBu3[i%3]]                                                           
    new_data['text'] = ds.data['text'] + [str(i)]
    ds.data = new_data

    i = i + 1

def callback(ds, wait):
    print(f'wait {wait}')
    doc.add_next_tick_callback(partial(async_callback, ds, wait))

# create a plot and style its properties                                                                                       
p = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)
p.border_fill_color = 'black'
p.background_fill_color = 'black'
p.outline_line_color = None
p.grid.grid_line_color = None

# add a text renderer to the plot (no data yet)                                                                                
r = p.text(x=[], y=[], text=[], text_color=[], text_font_size="26px",
           text_baseline="middle", text_align="center")

# add a button widget and configure with the call back                                                                         
button = Button(label="Press Me with no wait")
button.on_click(partial(callback, r.data_source, 0))


# create a plot and style its properties                                                                                       
p2 = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)
p2.border_fill_color = 'black'
p2.background_fill_color = 'black'
p2.outline_line_color = None
p2.grid.grid_line_color = None

# add a text renderer to the plot (no data yet)                                                                                
r2 = p2.text(x=[], y=[], text=[], text_color=[], text_font_size="26px",
           text_baseline="middle", text_align="center")

button2 = Button(label="Press Me with wait 10 second")
button2.on_click(partial(callback,r2.data_source, 10))

# put the button and plot in a layout and add to the document                                                                  
doc.add_root(row(column(button,p), column(button2,p2)))

I try the same thing with Panel and it works:

import panel as pn
import asyncio

button = pn.widgets.Button(name='Click me!')
text = pn.widgets.StaticText()
async def run_async(event):
    text.value = f'Running {event.new}'
    await asyncio.sleep(0)
    text.value = f'Finished {event.new}'
button.on_click(run_async)
row = pn.Row(button, text)

button2 = pn.widgets.Button(name='Click me! with 10s wait')
text2 = pn.widgets.StaticText()                                                
async def run_async2(event):
    text2.value = f'Running {event.new}'                                       
    await asyncio.sleep(10)
    text2.value = f'Finished {event.new}'                                      
button2.on_click(run_async2)                                                   
row2 = pn.Row(button2, text2)                                                  

col = pn.Column(row, row2)
col.show()

I am not sure what Panel may have done on top of (outside) Bokeh, but standard Bokeh callbacks only synchronize when the callback function exits. So if you want a state update to happen “in the middle” you have to split the callback up. Do the first chunk of work in the callback normally, and then put the second chunk of work in a separate callback that you explicitly schedule with Document.add_next_tick_callback.