pythonpython-asyncioctypespython-bleak

Await inside ctypes callback with loop already running


I need to perform a series of read/write tasks inside a ctypes callback function, but still inside an async task that is responsible for giving such read/write tasks:

async def main(ble_address):
    async with BleakClient(ble_address) as client:
        def my_io_callback(...)
            # await client.read_gatt_char(UUID) ???
            # await client.write_gatt_char(UUID, ...) ???
        my_c_function(to_c_function(my_io_callback))

asyncio.run(main(ble_address))

I can't move the whole async block inside the callback because it will be called several times and I need the device to be connected during the entire interaction.

What's the proper way of dealing with this situation? Answers I've seen so far haven't quite cover this particular case.


Solution

  • Without knowing the fine details of your code it is possible that the following is a solution:

    I would call an async function from a sync functions using the run_coro function below. The first time this function is called, it creates a new daemon thread running an event loop and then uses asyncio.run_coroutine_threadsafe to run the async function in the new thread:

    import asyncio
    import threading
    
    _event_loop = None
    
    def run_async(coro):
        """await a coroutine from a synchronous function/method."""
    
        global _event_loop
    
        # Lazily create a new event loop and a daemon thread that will run it:
        if not _event_loop:
            _event_loop = asyncio.new_event_loop()
            threading.Thread(target=_event_loop.run_forever, name="Async Runner", daemon=True).start()
    
        return asyncio.run_coroutine_threadsafe(coro, _event_loop).result()
    
    async def main():
        sync_function()  # call a sync function
    
    def sync_function():
        # We want to await an async_function, so:
        result = run_async(async_function())
        print(result)
    
    async def async_function():
        await asyncio.sleep(1)
        return 7
    
    asyncio.run(main())
    

    Prints:

    7
    

    Note that the call to run_coro will block the main thread until the async function being invoked completes. But you stated in one of your comments:

    Unfortunately this is impossible because the c callback (third party library) is supposed to block until the end of the IO operation.

    So this blocking behavior should be exactly what you want.