Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a coroutine synchronously

Imagine the following very common situation: you have written a long and complicated function and realize that some of the code should be extracted into a seperate function for reuse and/or readability. Usually, this extra function call will not change the semantics of your program.

However, now imagine that your function is a coroutine and the code you want to extract contains at least one asyncronous call. Extracting it into a separate function now suddenly changes your programs semantics by inserting a new point on which the coroutine yields, the event loop takes control and any other coroutine could be scheduled in between.

Example before:

async def complicated_func():
    foo()
    bar()
    await baz()

Example after:

async def complicated_func():
    foo()
    await extracted_func()

async def extracted_func():
    bar()
    await baz()

In the example before, the complicated_func is guaranteed not to be suspended between calling foo() and calling bar(). After refactoring, this guarantee is lost.

My question is this: is it possible to call extracted_func() such that it is executed immediately as if its code would be inline? Or is there some other way to perform such common refactoring tasks without changing the programs semantics?

like image 785
Max Tet Avatar asked Jul 03 '17 09:07

Max Tet


1 Answers

After refactoring, this guarantee is lost.

It's actually not.

Is it possible to call extracted_func() such that it is executed immediately as if its code would be inline?

That's already the case.

await some_coroutine() means that some_coroutine is likely to give to control back to the event loop, but it's not going to do so until it actually awaits a future (e.g some I/O operation).

Consider this example:

import asyncio

async def coro():
    print(1)
    await asyncio.sleep(0)
    print(3)

async def main():
    loop.call_soon(print, 2)
    await coro()

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Notice how 2 gets printed between 1 and 3 as expected.

That also means it's possible to freeze the event loop by writing such code:

async def coro():
    return

async def main():
    while True:
        await coro()

In this case, the event loop never gets a chance to run another task.

like image 142
Vincent Avatar answered Nov 17 '22 03:11

Vincent