This article explains how Python uses
asyncio.create_task()
functions to run multiple tasks concurrently.
Simulate long-running operations
To simulate long-running operations, you can use asyncio
the package's sleep()
coroutines. This sleep()
function delays for the specified number of seconds:
<span style="background-color:#f8f8f8"><span style="color:#212529"><code class="language-python"><span style="color:#f47067">await</span> asyncio.sleep(seconds)</code></span></span>
Because sleep()
it is a coroutine, you need to use await
keywords. For example, the following sleep()
simulates API calls using coroutines:
import asyncio |
|
async def call_api(message, result=1000, delay=3): |
|
print(message) |
|
await asyncio.sleep(delay) |
|
return result |
call_api()
is a coroutine. It displays a message, pauses for the specified number of seconds (the default is three seconds), and then returns the result.
The following program is used call_api()
twice and measures the time it takes to complete:
import asyncio |
|
import time |
|
async def call_api(message, result=1000, delay=3): |
|
print(message) |
|
await asyncio.sleep(delay) |
|
return result |
|
async def main(): |
|
start = time.perf_counter() |
|
price = await call_api('Get stock price of GOOG...', 300) |
|
print(price) |
|
price = await call_api('Get stock price of APPL...', 400) |
|
print(price) |
|
end = time.perf_counter() |
|
print(f'It took {
round(end-start,0)} second(s) to complete.') |
|
asyncio.run(main()) |
output:
Get stock price of GOOG... |
|
300 |
|
Get stock price of APPL... |
|
400 |
|
It took 6.0 second(s) to complete. |
First, start a timer to measure time using time
the module's function:perf_counter()
<span style="background-color:#f8f8f8"><span style="color:#212529"><code class="language-python"> start = time.perf_counter()</code></span></span>
2. Call call_api()
the coroutine and display the result:
price = await call_api('Get stock price of GOOG...', 300) |
|
print(price) |
3. call_api()
The second call:
price = await call_api('Get stock price of APPL...', 400) |
|
print(price) |
Finally, show how long the program took to complete:
end = time.perf_counter() |
|
print(f'It took {
round(end-start,0)} second(s) to complete.') |
Since each call_api()
takes three seconds, calling it twice takes six seconds.
In this example, we directly call a coroutine without running it in the event loop. Instead, we get a coroutine object and use await
the keyword to execute it and get a result.
We use async
and await
to write asynchronous code but it cannot run concurrently, to run multiple operations at the same time we need to use something called tasks.
Introduction to Python tasks
A task is a wrapper around a coroutine that schedules the coroutine to run on the event loop as soon as possible.
Scheduling and execution occurs in a non-blocking fashion, and tasks can be created and other code executed immediately while the task is running.
Note that a task is different from await
a keyword that blocks the entire coroutine until the operation completes and produces a result.
The important thing is that multiple tasks can be created and scheduled to run simultaneously in the event loop at once.
To create a task, a coroutine needs to be passed to asyncio
the package's create_task()
function. This create_task()
function returns an Task
object.
The following program illustrates how to create two call_api()
tasks that schedule and execute coroutines:
import asyncio |
|
import time |
|
async def call_api(message, result=1000, delay=3): |
|
print(message) |
|
await asyncio.sleep(delay) |
|
return result |
|
async def main(): |
|
start = time.perf_counter() |
|
task_1 = asyncio.create_task( |
|
call_api('Get stock price of GOOG...', 300) |
|
) |
|
task_2 = asyncio.create_task( |
|
call_api('Get stock price of APPL...', 300) |
|
) |
|
price = await task_1 |
|
print(price) |
|
price = await task_2 |
|
print(price) |
|
end = time.perf_counter() |
|
print(f'It took {
round(end-start,0)} second(s) to complete.') |
|
asyncio.run(main()) |
output:
Get stock price of GOOG... |
|
Get stock price of APPL... |
|
300 |
|
300 |
|
It took 3.0 second(s) to complete. |
await
It is important to use the keyword await for a task at some point in the program .
If we don't use await
the keyword, Python will schedule the task to run but asyncio.run()
stop it when the event loop is closed.
The following figure illustrates the execution flow of the program:
Finally, show main()
how long the function took to complete:
end = time.perf_counter() |
|
print(f'It took {
round(end-start,0)} second(s) to complete.') |
By using this create_task()
function, the program is much faster. The more tasks you run, the faster it will be.
Run other tasks while waiting
While call_api
running, other tasks can be run. For example, the following program call_api
displays a message every second while waiting for a task:
import asyncio |
|
import time |
|
async def call_api(message, result=1000, delay=3): |
|
print(message) |
|
await asyncio.sleep(delay) |
|
return result |
|
async def show_message(): |
|
for _ in range(3): |
|
await asyncio.sleep(1) |
|
print('API call is in progress...') |
|
async def main(): |
|
start = time.perf_counter() |
|
message_task = asyncio.create_task( |
|
show_message() |
|
) |
|
task_1 = asyncio.create_task( |
|
call_api('Get stock price of GOOG...', 300) |
|
) |
|
task_2 = asyncio.create_task( |
|
call_api('Get stock price of APPL...', 300) |
|
) |
|
price = await task_1 |
|
print(price) |
|
price = await task_2 |
|
print(price) |
|
await message_task |
|
end = time.perf_counter() |
|
print(f'It took {
round(end-start,0)} second(s) to complete.') |
|
asyncio.run(main()) |
output:
Get stock price of GOOG... |
|
Get stock price of APPL... |
|
API call is in progress... |
|
API call is in progress... |
|
API call is in progress... |
|
300 |
|
300 |
Summarize
- A task is a wrapper around a coroutine that schedules the coroutine to run on the event loop as soon as possible.
- Create tasks using
asyncio
library functions.create_task()
- Use the keyword task with the task at some point in the program
await
so thatasyncio.run()
the task can be completed before the function closes the event loop.