Can't use asynctosync in bokeh document

I’m a bit confused with what parts of bokeh are async and what aren’t - in my bokeh server application, if I need to run something that can only run in a synchronous environment then I need to do:

def app(doc):
    ...
    def sync_only():
        ...
   
   executor = concurrent.futures.ThreadPoolExecutor()
   future = executor.submit(sync_only)
   result = future.result()
   executor.shutdown()

Which is surprising in of itself since it’s not declared as async def app

Then in another instance I wanted to run an async function in app so I figured I had to do - asgiref.async_to_sync(async_function)(arg), but that doesn’t work either (error - You cannot use AsyncToSync in the same thread as an async event loop - just await the async function directly)… Ok so then if I just await it it of course complains that the function needs to be declared async, so I declare it async and now it’s not even getting run at all (I’m assuming this is due to it not being awaited higher up)… so I’m at a bit of a loss given it seems to be in between async and sync.

Thanks,
Ryan

That’s because nothing in your code is async from the POV of Python. It’s just good old multithreading. By the way, in this case it doesn’t give you anything at all - your code still waits for the future to be resolved, so no other code gets a chance to run. Make sure to read this section to understand it better: Bokeh server — Bokeh 3.3.2 Documentation

Apart from that link, there’s nothing here specific to Bokeh. It’s just a plain issue of async vs sync code, as old as the async code itself.

It’s really important to bear in mind that a Bokeh app code itself is really like a blueprint, and is distinct from what viewers interact with, which is the result of that blueprint.

When the server runs the app code, it is building up a Bokeh document, to service a new browser session. Once that is done, the app code is never run again for that session. Now, the “blueprint” might specify callbacks to set up for various events, and those callbacks might run, but that’s only after the app code has done its thing (once) to set everything else up. Those callbacks an be async functions, and so you could await things inside async callbacks.

But you are correct that you cannot currently await inside the “top level” app code itself. I suppose it would be possible to allow FunctionHandler to accept async def app functions. I don’t think it would add benefit in the majority of cases. Where it might have a marginal benefit is if the setup of the document itself needs to do what would otherwise be blocking i/o, but there’s also lifecycle hooks that can be used for things like that currently.

if you are just looking to offload blocking work in to threads (it’s not completely clear) then the links @p-himik gave are the best resource.

Thanks Eugene and Bryan for your in-depth responses -

Let me clarify a little further on the async/sync issue. So I am trying to run something synchronous (an database operation) that however peforms a check to make sure it is not in an async-environment (by checking if the event loop is null I believe). If I run this function just inside a bokeh application (this an application embedded in django) then this check fails because it finds what I’m assuming to be the bokeh/tornado event loop. So I run this operation in a separate thread, yay, that works :stuck_out_tongue_winking_eye: .

Now when I am running a separate async function - stub:

async def send():
  ...

It cannot be awaited inside the bokeh application (because await can’t exist inside a def foo, and so it has to be converted to sync using async_to_sync. So my main question is it seems like the main app code is running inside an event loop but can’t take advantage of ‘async/await’?

It is extremely useful to know that those callbacks can by async callbacks, thanks! I read that but I had forgotten :slight_smile: That pretty much fixes my issue, now it’s just a curiousity!

Can the lifecycle hooks be async?

On a semi-related note, I think it might be rather useful to make the bokehjs event interfaces a public part of the api, so that users can trigger their own events and then handle them with callbacks in the python code. This assists with embedding bokeh even more, for instance I’ve set up various html elements which when they change I trigger an event on the event_manager and bokeh sends it to the python client (right now it’s just a hack since the javascript object looks the same so bokeh doesn’t notice) and then I can for instance change the bokeh document or in my embedded case update a database entry or something!

Thanks,
Ryan