python-eel / Eel

A little Python library for making simple Electron-like HTML/JS GUI apps
MIT License
6.5k stars 592 forks source link

async support #691

Open rraammiinn opened 1 year ago

rraammiinn commented 1 year ago

can you add async support to it ? I tried to use async-eel module but it didn't work .

ajmirsky commented 1 year ago

eel uses greenlets which was a python library for coroutines before asyncio was introduced. so using greenlet coroutines instead of asyncio coroutines is probably the cleanest approach.

But if you're dependent on an already existing async library, you can spawn eel into a greenlet (by passing block=False to eel.start) and then run your asyncio coroutines. Since you're running two different types of coroutines, make sure you "await" the greenlets too:


async def my_async_function():
    print("my async function is running")
    await asyncio.sleep(0) # allows other asyncio coroutines time to run
    eel.sleep(0)  # allows the eel greenlet coroutines time to run

def main()
    eel.start(...., block=False) 

    asyncio.run(my_async_function())

The alternative is to run your async functions in a separate thread (I find this cleaner) and then let eel run its greenlets in the main thread.

from threading import Thread

class AsyncioThread(Thread):
    daemon = True
    def __init__(self):
        super().__init__()
        self.loop = get_event_loop()

    async def my_async_function():
        print("running my async function")
        await asyncio.sleep(1)

    async def launch(self):
        task1 = self.loop.create_task(self.my_async_function())
        await asyncio.gather(task1)

    def run(self):
        self.loop.run_until_complete(self.launch())

def main():

    asyncio_thread = AsyncioThread()
    asyncio_thread.start()

    eel.start(...)

for either method, if you need any @eel.expose function to interact with your async functions, use the threadsafe versions -- run_coroutine_threadsafe and call_soon_threadsafe --since eel will be calling from a separate thread. keep in mind that these methods don't necessarily use the event loop's exception handling so you might need to handle those cases separately.

also, it is good practice to clean up your coroutines before exiting the app/thread, so in the close_callback function passed to eel.start, you should call cancel() on each task by holding on to a reference to each task (eg self.task_list.append(task1)) or using a method to find all running tasks in an event loop.