3

I recently started looking into asynchronous programming in Python. Let's say we want to run a function asynchronously, an example below:

async def print_i_async(no):
    print("Async: Preparing print of " + str(no))
    await asyncio.sleep(1)
    print(str(no))

async def main_async(no):
    await asyncio.gather(*(print_i_async(i) for i in range(no)))

asyncio.run(main_async(no))

This will as expected work asynchronously. It's not clear to me, however, why would we use asynchronous functions if not with asyncio.gather(). For example:

def print_i_serial(no):
    print("Serial: Preparing print of " + str(no))
    time.sleep(1)
    print(str(no))

for i in range(5):
    print_i_serial(i)

for i in range(5):
    asyncio.run(print_i_async(i))

These two functions produce the same result. Am I missing something? Is there any reason we would use an async def if we don't use asyncio.gather(), given this is how we actually get asynchronous results?

1 Answer 1

7

There are many reasons to use asyncio besides gather.

What you are really asking is: are there more ways to create concurrent executions besides gather?

To that the answer is yes.

Yes, gather is one of the simplest and most straightforward examples for creating concurrency with asyncio, but it's not limited to gather. What gather does is creating a bunch of awaitables (if needed, for example coroutines are wrapped in a task) to wait for and return the result once all the futures are ready (and a bunch of other stuff such as propagating cancellation).

Let's examine just two more examples of ways to achieve concurrency:

  1. as_completed - similarly to gather, you send in a bunch of awaitables, but instead of waiting for all of them to be ready, this method returns you the futures as they become ready, unordered.
  2. Another example is to create tasks yourself, e.g. with event_loop.create_task(). This will allow you to create a task that will run on the event loop, which you can later await. In the meantime (until you await the task) you can continue running other code, and basically achieve concurrency (note the task will not run straightaway, but only when you yield control back to the event loop, and it handles the task).

There are many more ways to achieve concurrency. You can start with these examples (the 2nd one is actually a general way you can use to create lots of different concurrent "topologies" of executions).

You can start by reading https://docs.python.org/3/library/asyncio-task.html

Sign up to request clarification or add additional context in comments.

4 Comments

thank you for the answer. I know I can create concurrent executions with gather() and create_task(). My question is: is there any reason to use asyncio if not to run concurrent tasks? In the second example I made, the result of the async def and of the def is the same and takes the same time. Is there any purpose of using async apart if we need concurrency (in which case we would use gather or create_task)?
@Lorenzo in the second examples both cases took the same time because they are both running serially. There's no concurrency so you can't expect different results. If your code does not have any concurrency, there is no reason to make it async. (Exception: Only reason to still have async functions in this case is if you are writing a library, and while your code alone is not concurrent, you do have I/O in your code, and you don't want to limit your users, then you should use coroutines).
Perfect, thank you! I wanted to be sure I didn't miss anything on the topic!
I've achieved nice & clean concurrency with asyncio python using this: elsampsa.github.io/task_thread/_build/html/index.html

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.