Explore async Python concepts with code examples and tutorials. Covers asyncio and Python features. Let's learn together!
As Geir Arne Hjelle says in his tutorial, Python 3.12 has the following improvements:
I won’t go into the details of these improvements, but for more information, you can read Geir’s article on RealPython or consult the official documentation.
asyncio.TaskGroup
which was demonstrated in ex_2_7.
The official documentation recommends using asyncio.TaskGroup
instead of asyncio.create_task
and asyncio.gather
by official documentation.asyncio.Runner
which demonstrated in ex_2_2.
As described in the official document,
it is a context manager that simplifies multiple async function calls in the same context.asyncio.Barrier
demonstrated in ex_4_4, used as a synchronization primitive.asyncio.eager_task_factory()
and asyncio.create_eager_task_factory()
functions to allow opting an event loop in to eager task execution, making some use-cases 2x to 5x faster.asyncio.current_task(loop=None)
for 4x-6x speedup.
This API returns the currently running asyncio.Task
instance, or None
if no task is running.
If loop=None
, then it calls get_running_loop()
to get the current event loop.asyncio.Queue.shutdown
which will raise asyncio.QueueShutDown
exception on put
and get
calls.Let’s start with eager_task_factory as something quite useful in asyncio world.
As this pull request (and its corresponding issue) demonstrates,
using asyncio.eager_task_factory
can speed up some async-heavy workloads to run up to 4-6 times.
Just take a look at ex_6_1 in which runs nearly 6 times faster than ex_6_2, however the same thing happens in both.
The idea is that we create a light coroutine named light_coro
(in which we just pass and don’t do anything)
and call it for one million times. We use eager_task_factory
and TaskGroup
in first example (ex_6_1)
but use gather
without eager_task_factory
in the second one (ex_6_2).
# ex_6_1
import asyncio
import time
async def light_coro():
pass
async def main():
print('Before running task group!')
time0 = time.time()
asyncio.get_event_loop().set_task_factory(asyncio.eager_task_factory)
async with asyncio.TaskGroup() as tg:
for _ in range(1000000):
tg.create_task(light_coro())
print(f'It took {time.time() - time0} to run!')
print('After running task group with eager task factory!')
asyncio.run(main())
# ex_6_2
import asyncio
import time
async def light_coro():
pass
async def main():
print('Before running gather!')
tasks_list = [light_coro() for _ in range(1000000)]
time0 = time.time()
tasks = asyncio.gather(*tasks_list)
await tasks
print(f'It took {time.time() - time0} to run!')
print('After running gather without eager task factory!')
print(f"tasks: {tasks}")
asyncio.run(main())
So we see that using eager_task_factory
can be quite useful.
And the official documentation’s recommendation to use TaskGroup
over gather
makes sense now.
Now let’s talk about the speed-up of asyncio.current_task
in Python3.12.
In the following example, we call asyncio.current_task
one million times.
Interestingly, this code runs nearly six times faster with Python 3.12 compared to other versions. Specifically,
it takes 0.05 seconds with Python 3.12 versus 0.33 seconds with Python 3.10 in my PC.
# ex_6_3
import asyncio
import time
async def main():
t1 = time.time()
for _ in range(10 ** 6):
asyncio.current_task()
t2 = time.time()
print(f'It took {t2-t1}s')
asyncio.run(main())
This is also an example from asyncio.Queue.shutdown
usage in python 3.13:
# ex_6_4
import asyncio
async def main():
queue = asyncio.Queue()
await queue.put(1)
queue.shutdown()
await queue.put(2)
asyncio.run(main())
Running example above will raise asyncio.QueueShutDown
error.