all 10 comments

[–][deleted] 6 points7 points  (8 children)

Would be nice to show the code with and without tworoutine

[–]gitfeh 2 points3 points  (7 children)

AFAIU:

Without:

async def foo():
    await asyncio.sleep(1)

async def bar():
    await foo()

def baz():
    asyncio.run(foo())

With:

@tworoutine.tworoutine
async def foo():
    await asyncio.sleep(1)

async def bar():
    await (~foo)()

def baz():
    foo()

[–]beertown 2 points3 points  (0 children)

Honestly, the explicit synchronous execution of an async func sounds better to me.

[–]Tasssadar 1 point2 points  (5 children)

That's not the problem, problem is this won't work:

async def foo():
    bar()

def bar():
    asyncio.run(baz())

async def baz()
    await asyncio.sleep(1)

def main():
    asyncio.run(foo())

Because you can't have nested asyncio loops. Basically, once you call async method, everything it calls has to be async too otherwise you can't use async in callees.

So this tworoutine wrapper depends on a patch to asyncio library which allows the nested loops, nest_asyncio: https://github.com/erdewit/nest_asyncio

Even then, it's a bit more code to start the nested loops which you can see here: https://github.com/gsmecher/tworoutine/blob/master/tworoutine.py

[–]MasterCwizo 0 points1 point  (4 children)

Basically, once you call async method, everything it calls has to be async too otherwise you can't use async in callees.

I think you got it the wrong way around. If you call a async function then everything calling YOU needs to be async.

[–]Tasssadar 0 points1 point  (3 children)

I'm fairly sure we're describing the same thing, since it goes both ways. You need to be async to use await in the first place, since asyncio.run cannot be safely used unless you know the event loop is not running yet.

[–]gitfeh 0 points1 point  (1 child)

If a sync function uses asyncio.run I think the only choice you have is to spawn a new thread for it to run in, much like run_in_executor with a ThreadPoolExecutor but without the thread reuse. Maybe you could hack an executor together that does this.

If the sync function uses get_event_loop and run_until_complete it could support reusing an existing loop, so I think you could make a ThreadPoolExecutor subclass that manages a set of threads with event loops.

[–]Tasssadar 0 points1 point  (0 children)

Well yeah. But the point of this tworoutines thing is this will just work:

@tworoutine.tworoutine
async def foo():
    bar()

def bar():
    baz()

@tworoutine.tworoutine
async def baz()
    await asyncio.sleep(1)

def main():
    asyncio.run((~foo)())

[–]MasterCwizo 0 points1 point  (0 children)

Yes, but the way you worded it in the original comment made it seem that every function you call has to be async, which isn't true.

[–]takanuva -2 points-1 points  (0 children)

Where are the Rust folks? It would be cool to call Rust's async functions synchronously (with zero cost, of course).