miguelgrinberg / greenletio

Asyncio integration with sync code using greenlets.
MIT License
150 stars 8 forks source link

async2sync doesn't seem to work in a Jupyter notebook #5

Closed jeanmonet closed 3 years ago

jeanmonet commented 3 years ago

Hi, first of all thanks to you and Mike (for the idea) and for putting this together.

I'm trying to use the await_ function inside a Jupyter notebook, but it doesn't seem to work.

Copy-pasting the example in https://github.com/miguelgrinberg/greenletio/blob/master/examples/sync_to_async_sleep.py:

import asyncio
import random
from greenletio import await_

def main():
    for i in range(10):
        await_(asyncio.sleep(random.random()))
        print(i)

main()

prints the i in the for loop but ends with:

<ipython-input-11-0bf9f04c9585>:8: RuntimeWarning: coroutine 'sleep' was never awaited
  await_(asyncio.sleep(random.random()))
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

Any idea why? Jupyter runs an outer event loop, but I'm not sure why this happens.

miguelgrinberg commented 3 years ago

Yeah, I think it is because of the pre-existing loop. There are basically two modes of operation that are supported:

You are in the 2nd case, so your code should be as follows:

import asyncio
import random
from greenletio import await_, async_

@async_
def main():
    for i in range(10):
        await_(asyncio.sleep(random.random()))
        print(i)

await main()
jeanmonet commented 3 years ago

Thanks, understood.

Generally, what would be the benefit of using the @async_ decorator instead of async def given that the function/coroutine still needs to be awaited with await?

My goal was to call an async coroutine from inside a sync definition, thus without using await. In the case of a Jupyter notebook, there would also be an outer event loop running, wrapping the sync function itself. But I'm not sure this is possible?

miguelgrinberg commented 3 years ago

The benefit of the decorator is that you can use it as a wrapper function instead of a decorator. Consider that the example I gave can also be written as follows:

def main():
    for i in range(10):
        await_(asyncio.sleep(random.random()))
        print(i)

await async_(main())

With this structure you can "asyncify" functions that are not yours, even ones that come from other packages. And unlike real async functions, the async_ decorator/wrapper needs to be applied to the top-level function only, and then all called functions can use await_.

jeanmonet commented 3 years ago

Many thanks for explanations!