Simplest async/await example possible in Python
To answer your questions I will provide 3 different solutions to the same problem.
case 1: just normal python
import timedef sleep(): print(f'Time: {time.time() - start:.2f}') time.sleep(1)def sum(name, numbers): total = 0 for number in numbers: print(f'Task {name}: Computing {total}+{number}') sleep() total += number print(f'Task {name}: Sum = {total}\n')start = time.time()tasks = [ sum("A", [1, 2]), sum("B", [1, 2, 3]),]end = time.time()print(f'Time: {end-start:.2f} sec')
output:
Task A: Computing 0+1Time: 0.00Task A: Computing 1+2Time: 1.00Task A: Sum = 3Task B: Computing 0+1Time: 2.01Task B: Computing 1+2Time: 3.01Task B: Computing 3+3Time: 4.01Task B: Sum = 6Time: 5.02 sec
case 2: async/await done wrong
import asyncioimport timeasync def sleep(): print(f'Time: {time.time() - start:.2f}') time.sleep(1)async def sum(name, numbers): total = 0 for number in numbers: print(f'Task {name}: Computing {total}+{number}') await sleep() total += number print(f'Task {name}: Sum = {total}\n')start = time.time()loop = asyncio.get_event_loop()tasks = [ loop.create_task(sum("A", [1, 2])), loop.create_task(sum("B", [1, 2, 3])),]loop.run_until_complete(asyncio.wait(tasks))loop.close()end = time.time()print(f'Time: {end-start:.2f} sec')
output:
Task A: Computing 0+1Time: 0.00Task A: Computing 1+2Time: 1.00Task A: Sum = 3Task B: Computing 0+1Time: 2.01Task B: Computing 1+2Time: 3.01Task B: Computing 3+3Time: 4.01Task B: Sum = 6Time: 5.01 sec
case 3: async/await done right (same as case 2 except the sleep
function)
import asyncioimport timeasync def sleep(): print(f'Time: {time.time() - start:.2f}') await asyncio.sleep(1)async def sum(name, numbers): total = 0 for number in numbers: print(f'Task {name}: Computing {total}+{number}') await sleep() total += number print(f'Task {name}: Sum = {total}\n')start = time.time()loop = asyncio.get_event_loop()tasks = [ loop.create_task(sum("A", [1, 2])), loop.create_task(sum("B", [1, 2, 3])),]loop.run_until_complete(asyncio.wait(tasks))loop.close()end = time.time()print(f'Time: {end-start:.2f} sec')
output:
Task A: Computing 0+1Time: 0.00Task B: Computing 0+1Time: 0.00Task A: Computing 1+2Time: 1.00Task B: Computing 1+2Time: 1.00Task A: Sum = 3Task B: Computing 3+3Time: 2.00Task B: Sum = 6Time: 3.01 sec
case 1
with case 2
give the same 5 seconds
, whereas case 3
just 3 seconds
. So the async/await done right
is faster.
The reason for the difference is within the implementation of sleep
function.
# case 1def sleep(): print(f'Time: {time.time() - start:.2f}') time.sleep(1)# case 2async def sleep(): print(f'Time: {time.time() - start:.2f}') time.sleep(1)# case 3async def sleep(): print(f'Time: {time.time() - start:.2f}') await asyncio.sleep(1)
sleep
function in case 1
and case 2
are the "same". They "sleep" without allowing others to use the resources.Whereas case 3
allows access to the resources when it is asleep.
In case 2
we added async
to the normal function. However the event loop will run it without interruption. Why? Because we didn't tell where the loop is allowed to interrupt your function to run another task.
In case 3
we told the event loop exactly where to interrupt the function to run another task. Where exactly?
# case 3async def sleep(): print(f'Time: {time.time() - start:.2f}') await asyncio.sleep(1) # <-- Right here!
More on this read here
Update 02/May/2020
Consider reading
is it possible to give a simple example showing how
async
/await
works, by using only these two keywords +asyncio.get_event_loop()
+run_until_complete
+ other Python code but no otherasyncio
functions?
This way it's possible to write code that works:
import asyncioasync def main(): print('done!')if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main())
But this way it's impossible to demonstrate why you need asyncio.
By the way, why do you need asyncio
, not just plain code? Answer is - asyncio
allows you to get performance benefit when you parallelize I/O blocking operations (like reading/writing to network). And to write useful example you need to use async implementation of those operations.
Please read this answer for more detailed explanation.
Upd:
ok, here's example that uses asyncio.sleep
to imitate I/O blocking operation and asyncio.gather
that shows how you can run multiple blocking operations concurrently:
import asyncioasync def io_related(name): print(f'{name} started') await asyncio.sleep(1) print(f'{name} finished')async def main(): await asyncio.gather( io_related('first'), io_related('second'), ) # 1s + 1s = over 1sif __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main())
Output:
first startedsecond startedfirst finishedsecond finished[Finished in 1.2s]
Note how both io_related
started then, after only one second, both done.
Python 3.7+ now has a simpler API (in my opinion) with a simpler wording (easier to remember than "ensure_future"): you can use create_task
which returns a Task object (that can be useful later to cancel the task if needed).
Basic example 1
import asyncioasync def hello(i): print(f"hello {i} started") await asyncio.sleep(4) print(f"hello {i} done")async def main(): task1 = asyncio.create_task(hello(1)) # returns immediately, the task is created await asyncio.sleep(3) task2 = asyncio.create_task(hello(2)) await task1 await task2asyncio.run(main()) # main loop
Result:
hello 1 started
hello 2 started
hello 1 done
hello 2 done
Basic example 2
If you need to get the return value of these async functions, then gather
is useful. The following example is inspired from the documentation, but unfortunately the doc doesn't show what gather
is really useful for: getting the return values!
import asyncioasync def factorial(n): f = 1 for i in range(2, n + 1): print(f"Computing factorial({n}), currently i={i}...") await asyncio.sleep(1) f *= i return fasync def main(): L = await asyncio.gather(factorial(2), factorial(3), factorial(4)) print(L) # [2, 6, 24]asyncio.run(main())
Expected output:
Computing factorial(2), currently i=2...
Computing factorial(3), currently i=2...
Computing factorial(4), currently i=2...
Computing factorial(3), currently i=3...
Computing factorial(4), currently i=3...
Computing factorial(4), currently i=4...
[2, 6, 24]
PS: even if you use asyncio
, and not trio
, the tutorial of the latter was helpful for me to grok Python asynchronous programming.