How to do a "real concurrent" corroutines

Question:

I’m trying to do this:

"An event loop runs in a thread (typically the main thread) and executes all callbacks and Tasks in its thread. While a Task is running in the event loop, no other Tasks can run in the same thread. When a Task executes an await expression, the running Task gets suspended, and the event loop executes the next Task."

https://docs.python.org/3/library/asyncio-dev.html#concurrency-and-multithreading

And I did this ugly example:

import asyncio

async def print_message(message):
    print(message)

async def int_sum(a, b):
    await print_message('start_sum')
    result = a + b
    await print_message('end_sum')
    return result

async def int_mul(a, b):
    await print_message('start_mul')
    result = a * b
    await print_message('end_mul')
    return result

async def main():
    result = await asyncio.gather(int_sum(4, 3), int_mul(4, 3))
    print(result)

asyncio.run(main())

With "secuential-like" results:

$ python async_test.py

start_sum
end_sum
start_mul
end_mul
[7, 12]

But I want a "corroutine-like" output:

$ python async_test.py

start_sum
start_mul
end_sum
end_mul
[7, 12]

How can I do that?

Note: I’m not looking for a asyncio.sleep(n) example, I’m looking for
"When a Task executes an await expression, the running Task gets suspended, and the event loop executes the next Task".

Asked By: Rubén

||

Answers:

The point is, Tasks only give the control back to the event loop with yield statement. In your example you already have three active tasks(add asyncio.all_tasks() in the first line of int_sum coroutine to confirm1) but for example int_sum is not cooperating. it doesn’t give the control back to the event loop. why ? Because you don’t have any yield.

A simple fix to this is to change your print_message to:

async def print_message(message):
    print(message)
    await asyncio.sleep(0)

if you see the source code of asyncio.sleep:

async def sleep(delay, result=None):
    """Coroutine that completes after a given time (in seconds)."""
    if delay <= 0:
        await __sleep0()
        return result
...

And this is the body of the __sleep0()(right above the sleep):

@types.coroutine
def __sleep0():
    """Skip one event loop run cycle.

    This is a private helper for 'asyncio.sleep()', used
    when the 'delay' is set to 0.  It uses a bare 'yield'
    expression (which Task.__step knows how to handle)
    instead of creating a Future object.
    """
    yield

Now your output should be:

start_sum
start_mul
end_sum
end_mul
[7, 12]

1 Note: you do have three tasks, asyncio.gather does that for you:

If any awaitable in aws is a coroutine, it is automatically scheduled
as a Task.

Answered By: S.B