Fundamentals 9 min read

Understanding Python asyncio Coroutines: Definition, Execution, Callbacks, and Event‑Loop Management

This article explains Python's asyncio asynchronous I/O model, how to define and run coroutines, use callbacks, manage multiple concurrent tasks with gather, compare run_until_complete with run_forever, and properly close the event loop, providing clear code examples throughout.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Understanding Python asyncio Coroutines: Definition, Execution, Callbacks, and Event‑Loop Management

Asynchronous I/O allows you to start an I/O operation without waiting for it to finish, enabling other work to proceed; in Python this is achieved with the asyncio library, which provides a concurrency model alongside threading and multiprocessing.

Asyncio does not provide true parallelism due to the GIL, and tasks that can be scheduled with asyncio are called coroutines. A coroutine is defined using the async def syntax, for example:

async def do_some_work(x):
    pass

You can verify a coroutine function with asyncio.iscoroutinefunction(do_some_work), which returns True. To give a coroutine some work, you might make it sleep for a few seconds:

async def do_some_work(x):
    print("Waiting " + str(x))
    await asyncio.sleep(x)

Coroutines can wait for futures, other coroutines, produce results, or raise exceptions. The await asyncio.sleep(x) call itself is a coroutine.

Calling a coroutine function returns a coroutine object, not a running task; you can check this with asyncio.iscoroutine(do_some_work(3)). To execute the coroutine you must schedule it on an event loop, either by awaiting it inside another coroutine, or by using ensure_future and running the loop:

loop = asyncio.get_event_loop()
loop.run_until_complete(do_some_work(3))

The run_until_complete call blocks until the coroutine finishes. Internally it wraps the coroutine in a future via asyncio.ensure_future, so you can also write:

loop.run_until_complete(asyncio.ensure_future(do_some_work(3)))

Full example:

import asyncio

async def do_some_work(x):
    print("Waiting " + str(x))
    await asyncio.sleep(x)

loop = asyncio.get_event_loop()
loop.run_until_complete(do_some_work(3))

Output:

Waiting 3
<three‑second pause>

Callbacks can be attached to futures to be notified when a coroutine finishes. For example:

def done_callback(futu):
    print('Done')

futu = asyncio.ensure_future(do_some_work(3))
futu.add_done_callback(done_callback)
loop.run_until_complete(futu)

In real projects multiple coroutines often run concurrently. Use asyncio.gather to schedule several coroutines together:

loop.run_until_complete(asyncio.gather(do_some_work(1), do_some_work(3)))

Or collect them in a list and unpack:

coros = [do_some_work(1), do_some_work(3)]
loop.run_until_complete(asyncio.gather(*coros))

Because the coroutines run concurrently, the total time equals the longest individual coroutine, not the sum of their durations.

The difference between run_until_complete and run_forever is that the former blocks until a single future finishes, while the latter keeps the loop running indefinitely until loop.stop() is called from within a coroutine.

# run_until_complete example
coro = do_some_work(3)
loop.run_until_complete(coro)

# run_forever example
asyncio.ensure_future(do_some_work(3))
loop.run_forever()

When using run_forever, you must stop the loop inside a coroutine after all work is done; otherwise the program will not exit.

Closing the loop with loop.close() is recommended to clean up resources; once closed, the loop cannot be reused.

loop.run_until_complete(do_some_work(1))
loop.run_until_complete(do_some_work(3))
loop.close()

Both asyncio.gather and asyncio.wait can wait for multiple coroutines, but they differ in return values and error handling; see external discussions for details.

Python lacks a native timer object like Boost.Asio, but you can simulate one with asyncio.sleep and callbacks:

async def timer(x, cb):
    futu = asyncio.ensure_future(asyncio.sleep(x))
    futu.add_done_callback(cb)
    await futu

t = timer(3, lambda futu: print('Done'))
loop.run_until_complete(t)
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

concurrencyAsynchronouscoroutineevent loopasyncio
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.