• United States+1
  • United Kingdom+44
  • Afghanistan (‫افغانستان‬‎)+93
  • Albania (Shqipëri)+355
  • Algeria (‫الجزائر‬‎)+213
  • American Samoa+1684
  • Andorra+376
  • Angola+244
  • Anguilla+1264
  • Antigua and Barbuda+1268
  • Argentina+54
  • Armenia (Հայաստան)+374
  • Aruba+297
  • Australia+61
  • Austria (Österreich)+43
  • Azerbaijan (Azərbaycan)+994
  • Bahamas+1242
  • Bahrain (‫البحرين‬‎)+973
  • Bangladesh (বাংলাদেশ)+880
  • Barbados+1246
  • Belarus (Беларусь)+375
  • Belgium (België)+32
  • Belize+501
  • Benin (Bénin)+229
  • Bermuda+1441
  • Bhutan (འབྲུག)+975
  • Bolivia+591
  • Bosnia and Herzegovina (Босна и Херцеговина)+387
  • Botswana+267
  • Brazil (Brasil)+55
  • British Indian Ocean Territory+246
  • British Virgin Islands+1284
  • Brunei+673
  • Bulgaria (България)+359
  • Burkina Faso+226
  • Burundi (Uburundi)+257
  • Cambodia (កម្ពុជា)+855
  • Cameroon (Cameroun)+237
  • Canada+1
  • Cape Verde (Kabu Verdi)+238
  • Caribbean Netherlands+599
  • Cayman Islands+1345
  • Central African Republic (République centrafricaine)+236
  • Chad (Tchad)+235
  • Chile+56
  • China (中国)+86
  • Christmas Island+61
  • Cocos (Keeling) Islands+61
  • Colombia+57
  • Comoros (‫جزر القمر‬‎)+269
  • Congo (DRC) (Jamhuri ya Kidemokrasia ya Kongo)+243
  • Congo (Republic) (Congo-Brazzaville)+242
  • Cook Islands+682
  • Costa Rica+506
  • Côte d’Ivoire+225
  • Croatia (Hrvatska)+385
  • Cuba+53
  • Curaçao+599
  • Cyprus (Κύπρος)+357
  • Czech Republic (Česká republika)+420
  • Denmark (Danmark)+45
  • Djibouti+253
  • Dominica+1767
  • Dominican Republic (República Dominicana)+1
  • Ecuador+593
  • Egypt (‫مصر‬‎)+20
  • El Salvador+503
  • Equatorial Guinea (Guinea Ecuatorial)+240
  • Eritrea+291
  • Estonia (Eesti)+372
  • Ethiopia+251
  • Falkland Islands (Islas Malvinas)+500
  • Faroe Islands (Føroyar)+298
  • Fiji+679
  • Finland (Suomi)+358
  • France+33
  • French Guiana (Guyane française)+594
  • French Polynesia (Polynésie française)+689
  • Gabon+241
  • Gambia+220
  • Georgia (საქართველო)+995
  • Germany (Deutschland)+49
  • Ghana (Gaana)+233
  • Gibraltar+350
  • Greece (Ελλάδα)+30
  • Greenland (Kalaallit Nunaat)+299
  • Grenada+1473
  • Guadeloupe+590
  • Guam+1671
  • Guatemala+502
  • Guernsey+44
  • Guinea (Guinée)+224
  • Guinea-Bissau (Guiné Bissau)+245
  • Guyana+592
  • Haiti+509
  • Honduras+504
  • Hong Kong (香港)+852
  • Hungary (Magyarország)+36
  • Iceland (Ísland)+354
  • India (भारत)+91
  • Indonesia+62
  • Iran (‫ایران‬‎)+98
  • Iraq (‫العراق‬‎)+964
  • Ireland+353
  • Isle of Man+44
  • Israel (‫ישראל‬‎)+972
  • Italy (Italia)+39
  • Jamaica+1876
  • Japan (日本)+81
  • Jersey+44
  • Jordan (‫الأردن‬‎)+962
  • Kazakhstan (Казахстан)+7
  • Kenya+254
  • Kiribati+686
  • Kosovo+383
  • Kuwait (‫الكويت‬‎)+965
  • Kyrgyzstan (Кыргызстан)+996
  • Laos (ລາວ)+856
  • Latvia (Latvija)+371
  • Lebanon (‫لبنان‬‎)+961
  • Lesotho+266
  • Liberia+231
  • Libya (‫ليبيا‬‎)+218
  • Liechtenstein+423
  • Lithuania (Lietuva)+370
  • Luxembourg+352
  • Macau (澳門)+853
  • Macedonia (FYROM) (Македонија)+389
  • Madagascar (Madagasikara)+261
  • Malawi+265
  • Malaysia+60
  • Maldives+960
  • Mali+223
  • Malta+356
  • Marshall Islands+692
  • Martinique+596
  • Mauritania (‫موريتانيا‬‎)+222
  • Mauritius (Moris)+230
  • Mayotte+262
  • Mexico (México)+52
  • Micronesia+691
  • Moldova (Republica Moldova)+373
  • Monaco+377
  • Mongolia (Монгол)+976
  • Montenegro (Crna Gora)+382
  • Montserrat+1664
  • Morocco (‫المغرب‬‎)+212
  • Mozambique (Moçambique)+258
  • Myanmar (Burma) (မြန်မာ)+95
  • Namibia (Namibië)+264
  • Nauru+674
  • Nepal (नेपाल)+977
  • Netherlands (Nederland)+31
  • New Caledonia (Nouvelle-Calédonie)+687
  • New Zealand+64
  • Nicaragua+505
  • Niger (Nijar)+227
  • Nigeria+234
  • Niue+683
  • Norfolk Island+672
  • North Korea (조선 민주주의 인민 공화국)+850
  • Northern Mariana Islands+1670
  • Norway (Norge)+47
  • Oman (‫عُمان‬‎)+968
  • Pakistan (‫پاکستان‬‎)+92
  • Palau+680
  • Palestine (‫فلسطين‬‎)+970
  • Panama (Panamá)+507
  • Papua New Guinea+675
  • Paraguay+595
  • Peru (Perú)+51
  • Philippines+63
  • Poland (Polska)+48
  • Portugal+351
  • Puerto Rico+1
  • Qatar (‫قطر‬‎)+974
  • Réunion (La Réunion)+262
  • Romania (România)+40
  • Russia (Россия)+7
  • Rwanda+250
  • Saint Barthélemy (Saint-Barthélemy)+590
  • Saint Helena+290
  • Saint Kitts and Nevis+1869
  • Saint Lucia+1758
  • Saint Martin (Saint-Martin (partie française))+590
  • Saint Pierre and Miquelon (Saint-Pierre-et-Miquelon)+508
  • Saint Vincent and the Grenadines+1784
  • Samoa+685
  • San Marino+378
  • São Tomé and Príncipe (São Tomé e Príncipe)+239
  • Saudi Arabia (‫المملكة العربية السعودية‬‎)+966
  • Senegal (Sénégal)+221
  • Serbia (Србија)+381
  • Seychelles+248
  • Sierra Leone+232
  • Singapore+65
  • Sint Maarten+1721
  • Slovakia (Slovensko)+421
  • Slovenia (Slovenija)+386
  • Solomon Islands+677
  • Somalia (Soomaaliya)+252
  • South Africa+27
  • South Korea (대한민국)+82
  • South Sudan (‫جنوب السودان‬‎)+211
  • Spain (España)+34
  • Sri Lanka (ශ්‍රී ලංකාව)+94
  • Sudan (‫السودان‬‎)+249
  • Suriname+597
  • Svalbard and Jan Mayen+47
  • Swaziland+268
  • Sweden (Sverige)+46
  • Switzerland (Schweiz)+41
  • Syria (‫سوريا‬‎)+963
  • Taiwan (台灣)+886
  • Tajikistan+992
  • Tanzania+255
  • Thailand (ไทย)+66
  • Timor-Leste+670
  • Togo+228
  • Tokelau+690
  • Tonga+676
  • Trinidad and Tobago+1868
  • Tunisia (‫تونس‬‎)+216
  • Turkey (Türkiye)+90
  • Turkmenistan+993
  • Turks and Caicos Islands+1649
  • Tuvalu+688
  • U.S. Virgin Islands+1340
  • Uganda+256
  • Ukraine (Україна)+380
  • United Arab Emirates (‫الإمارات العربية المتحدة‬‎)+971
  • United Kingdom+44
  • United States+1
  • Uruguay+598
  • Uzbekistan (Oʻzbekiston)+998
  • Vanuatu+678
  • Vatican City (Città del Vaticano)+39
  • Venezuela+58
  • Vietnam (Việt Nam)+84
  • Wallis and Futuna+681
  • Western Sahara (‫الصحراء الغربية‬‎)+212
  • Yemen (‫اليمن‬‎)+967
  • Zambia+260
  • Zimbabwe+263
  • Åland Islands+358
Thanks! We'll be in touch in the next 12 hours
Oops! Something went wrong while submitting the form.

Implementing Async Features in Python - A Step-by-step Guide

Asynchronous programming is a characteristic of modern programming languages that allows an application to perform various operations without waiting for any of them. Asynchronicity is one of the big reasons for the popularity of Node.js.

We have discussed Python’s asynchronous features as part of our previous post: an introduction to asynchronous programming in Python. This blog is a natural progression on the same topic. We are going to discuss async features in Python in detail and look at some hands-on examples.

Consider a traditional web scraping application that needs to open thousands of network connections. We could open one network connection, fetch the result, and then move to the next ones iteratively. This approach increases the latency of the program. It spends a lot of time opening a connection and waiting for others to finish their bit of work.

On the other hand, async provides you a method of opening thousands of connections at once and swapping among each connection as they finish and return their results. Basically, it sends the request to a connection and moves to the next one instead of waiting for the previous one’s response. It continues like this until all the connections have returned the outputs.  

Image for post

Source: phpmind

From the above chart, we can see that using synchronous programming on four tasks took 45 seconds to complete, while in asynchronous programming, those four tasks took only 20 seconds.

Where Does Asynchronous Programming Fit in the Real-world?

Asynchronous programming is best suited for popular scenarios such as:

1. The program takes too much time to execute.

2. The reason for the delay is waiting for input or output operations, not computation.

3. For the tasks that have multiple input or output operations to be executed at once.

And application-wise, these are the example use cases:

  • Web Scraping
  • Network Services

Difference Between Parallelism, Concurrency, Threading, and Async IO

Because we discussed this comparison in detail in our previous post, we will just quickly go through the concept as it will help us with our hands-on example later.

Parallelism involves performing multiple operations at a time. Multiprocessing is an example of it. It is well suited for CPU bound tasks.

Concurrency is slightly broader than Parallelism. It involves multiple tasks running in an overlapping manner.

Threading – a thread is a separate flow of execution. One process can contain multiple threads and each thread runs independently. It is ideal for IO bound tasks.

Async IO is a single-threaded, single-process design that uses cooperative multitasking. In simple words, async IO gives a feeling of concurrency despite using a single thread in a single process.


A comparison in concurrency and parallelism
Fig:- A comparison in concurrency and parallelism


Components of Async IO Programming

Let’s explore the various components of Async IO in depth. We will also look at an example code to help us understand the implementation.

1. Coroutines

Coroutines are mainly generalization forms of subroutines. They are generally used for cooperative tasks and behave like Python generators.

An async function uses the await keyword to denote a coroutine. When using the await keyword, coroutines release the flow of control back to the event loop.

To run a coroutine, we need to schedule it on the event loop. After scheduling, coroutines are wrapped in Tasks as a Future object.

Example:

In the below snippet, we called async_func from the main function. We have to add the await keyword while calling the sync function. As you can see, async_func will do nothing unless the await keyword implementation accompanies it.

import asyncio
async def async_func():
print('Velotio ...')
await asyncio.sleep(1)
print('... Technologies!')
async def main():
async_func()#this will do nothing because coroutine object is created but not awaited
await async_func()
asyncio.run(main())
view raw async.py hosted with ❤ by GitHub

Output

RuntimeWarning: coroutine 'async_func' was never awaited
async_func()#this will do nothing because coroutine object is created but not awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Velotio ...
... Blog!

2. Tasks

Tasks are used to schedule coroutines concurrently.

When submitting a coroutine to an event loop for processing, you can get a Task object, which provides a way to control the coroutine’s behavior from outside the event loop.

Example:

In the snippet below, we are creating a task using create_task (an inbuilt function of asyncio library), and then we are running it.

import asyncio
async def async_func():
print('Velotio ...')
await asyncio.sleep(1)
print('... Blog!')
async def main():
task = asyncio.create_task (async_func())
await task
asyncio.run(main())
view raw async_task.py hosted with ❤ by GitHub

Output

Velotio ...
... Blog!

3 Event Loops

This mechanism runs coroutines until they complete. You can imagine it as while(True) loop that monitors coroutine, taking feedback on what’s idle, and looking around for things that can be executed in the meantime.

It can wake up an idle coroutine when whatever that coroutine is waiting on becomes available.

Only one event loop can run at a time in Python.

Example:

In the snippet below, we are creating three tasks and then appending them in a list and executing all tasks asynchronously using get_event_loop, create_task and the await function of the asyncio library.

import asyncio
async def async_func(task_no):
print(f'{task_no} :Velotio ...')
await asyncio.sleep(1)
print(f'{task_no}... Blog!')
async def main():
taskA = loop.create_task (async_func('taskA'))
taskB = loop.create_task(async_func('taskB'))
taskC = loop.create_task(async_func('taskC'))
await asyncio.wait([taskA,taskB,taskC])
if __name__ == "__main__":
try:
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
except :
pass
view raw async_loop.py hosted with ❤ by GitHub

Output

taskA :Velotio ...
taskB :Velotio ...
taskC :Velotio ...
taskA... Blog!
taskB... Blog!
taskC... Blog!

Future

A future is a special, low-level available object that represents an eventual result of an asynchronous operation.

When a Future object is awaited, the co-routine will wait until the Future is resolved in some other place.

We will look into the sample code for Future objects in the next section.

A Comparison Between Multithreading and Async IO

Before we get to Async IO, let’s use multithreading as a benchmark and then compare them to see which is more efficient.

For this benchmark, we will be fetching data from a sample URL (the Velotio Career webpage) with different frequencies, like once, ten times, 50 times, 100 times, 500 times, respectively.

We will then compare the time taken by both of these approaches to fetch the required data.

Implementation

Code of Multithreading:

import requests
import time
from concurrent.futures import ProcessPoolExecutor
def fetch_url_data(pg_url):
try:
resp = requests.get(pg_url)
except Exception as e:
print(f"Error occured during fetch data from url{pg_url}")
else:
return resp.content
def get_all_url_data(url_list):
with ProcessPoolExecutor() as executor:
resp = executor.map(fetch_url_data, url_list)
return resp
if __name__=='__main__':
url = "https://www.velotio.com/careers"
for ntimes in [1,10,50,100,500]:
start_time = time.time()
responses = get_all_url_data([url] * ntimes)
print(f'Fetch total {ntimes} urls and process takes {time.time() - start_time} seconds')

Output

Fetch total 1 urls and process takes 1.8822264671325684 seconds
Fetch total 10 urls and process takes 2.3358211517333984 seconds
Fetch total 50 urls and process takes 8.05638575553894 seconds
Fetch total 100 urls and process takes 14.43302869796753 seconds
Fetch total 500 urls and process takes 65.25404500961304 seconds

ProcessPoolExecutor is a Python package that implements the Executor interface. The fetch_url_data is a function to fetch the data from the given URL using the requests python package, and the get_all_url_data function is used to map the fetch_url_data function to the lists of URLs.

Async IO Programming Example:

import asyncio
import time
from aiohttp import ClientSession, ClientResponseError
async def fetch_url_data(session, url):
try:
async with session.get(url, timeout=60) as response:
resp = await response.read()
except Exception as e:
print(e)
else:
return resp
return
async def fetch_async(loop, r):
url = "https://www.velotio.com/careers"
tasks = []
async with ClientSession() as session:
for i in range(r):
task = asyncio.ensure_future(fetch_url_data(session, url))
tasks.append(task)
responses = await asyncio.gather(*tasks)
return responses
if __name__ == '__main__':
for ntimes in [1, 10, 50, 100, 500]:
start_time = time.time()
loop = asyncio.get_event_loop()
future = asyncio.ensure_future(fetch_async(loop, ntimes))
loop.run_until_complete(future) #will run until it finish or get any error
responses = future.result()
print(f'Fetch total {ntimes} urls and process takes {time.time() - start_time} seconds')
view raw final_async.py hosted with ❤ by GitHub

Output

Fetch total 1 urls and process takes 1.3974951362609863 seconds
Fetch total 10 urls and process takes 1.4191942596435547 seconds
Fetch total 50 urls and process takes 2.6497368812561035 seconds
Fetch total 100 urls and process takes 4.391665458679199 seconds
Fetch total 500 urls and process takes 4.960426330566406 seconds

We need to use the get_event_loop function to create and add the tasks. For running more than one URL, we have to use ensure_future and gather function.

The fetch_async function is used to add the task in the event_loop object and the fetch_url_data function is used to read the data from the URL using the session package. The future_result method returns the response of all the tasks.

Results:

As you can see from the plot, async programming is much more efficient than multi-threading for the program above. 

The graph of the multithreading program looks linear, while the asyncio program graph is similar to logarithmic.


AsyncIO vs Multithreading program

Conclusion

As we saw in our experiment above, Async IO showed better performance with the efficient use of concurrency than multi-threading.

Async IO can be beneficial in applications that can exploit concurrency. Though, based on what kind of applications we are dealing with, it is very pragmatic to choose Async IO over other implementations.

We hope this article helped further your understanding of the async feature in Python and gave you some quick hands-on experience using the code examples shared above.

Get the latest engineering blogs delivered straight to your inbox.
No spam. Only expert insights.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Did you like the blog? If yes, we're sure you'll also like to work with the people who write them - our best-in-class engineering team.

We're looking for talented developers who are passionate about new emerging technologies. If that's you, get in touch with us.

Explore current openings

Implementing Async Features in Python - A Step-by-step Guide

Asynchronous programming is a characteristic of modern programming languages that allows an application to perform various operations without waiting for any of them. Asynchronicity is one of the big reasons for the popularity of Node.js.

We have discussed Python’s asynchronous features as part of our previous post: an introduction to asynchronous programming in Python. This blog is a natural progression on the same topic. We are going to discuss async features in Python in detail and look at some hands-on examples.

Consider a traditional web scraping application that needs to open thousands of network connections. We could open one network connection, fetch the result, and then move to the next ones iteratively. This approach increases the latency of the program. It spends a lot of time opening a connection and waiting for others to finish their bit of work.

On the other hand, async provides you a method of opening thousands of connections at once and swapping among each connection as they finish and return their results. Basically, it sends the request to a connection and moves to the next one instead of waiting for the previous one’s response. It continues like this until all the connections have returned the outputs.  

Image for post

Source: phpmind

From the above chart, we can see that using synchronous programming on four tasks took 45 seconds to complete, while in asynchronous programming, those four tasks took only 20 seconds.

Where Does Asynchronous Programming Fit in the Real-world?

Asynchronous programming is best suited for popular scenarios such as:

1. The program takes too much time to execute.

2. The reason for the delay is waiting for input or output operations, not computation.

3. For the tasks that have multiple input or output operations to be executed at once.

And application-wise, these are the example use cases:

  • Web Scraping
  • Network Services

Difference Between Parallelism, Concurrency, Threading, and Async IO

Because we discussed this comparison in detail in our previous post, we will just quickly go through the concept as it will help us with our hands-on example later.

Parallelism involves performing multiple operations at a time. Multiprocessing is an example of it. It is well suited for CPU bound tasks.

Concurrency is slightly broader than Parallelism. It involves multiple tasks running in an overlapping manner.

Threading – a thread is a separate flow of execution. One process can contain multiple threads and each thread runs independently. It is ideal for IO bound tasks.

Async IO is a single-threaded, single-process design that uses cooperative multitasking. In simple words, async IO gives a feeling of concurrency despite using a single thread in a single process.


A comparison in concurrency and parallelism
Fig:- A comparison in concurrency and parallelism


Components of Async IO Programming

Let’s explore the various components of Async IO in depth. We will also look at an example code to help us understand the implementation.

1. Coroutines

Coroutines are mainly generalization forms of subroutines. They are generally used for cooperative tasks and behave like Python generators.

An async function uses the await keyword to denote a coroutine. When using the await keyword, coroutines release the flow of control back to the event loop.

To run a coroutine, we need to schedule it on the event loop. After scheduling, coroutines are wrapped in Tasks as a Future object.

Example:

In the below snippet, we called async_func from the main function. We have to add the await keyword while calling the sync function. As you can see, async_func will do nothing unless the await keyword implementation accompanies it.

import asyncio
async def async_func():
print('Velotio ...')
await asyncio.sleep(1)
print('... Technologies!')
async def main():
async_func()#this will do nothing because coroutine object is created but not awaited
await async_func()
asyncio.run(main())
view raw async.py hosted with ❤ by GitHub

Output

RuntimeWarning: coroutine 'async_func' was never awaited
async_func()#this will do nothing because coroutine object is created but not awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Velotio ...
... Blog!

2. Tasks

Tasks are used to schedule coroutines concurrently.

When submitting a coroutine to an event loop for processing, you can get a Task object, which provides a way to control the coroutine’s behavior from outside the event loop.

Example:

In the snippet below, we are creating a task using create_task (an inbuilt function of asyncio library), and then we are running it.

import asyncio
async def async_func():
print('Velotio ...')
await asyncio.sleep(1)
print('... Blog!')
async def main():
task = asyncio.create_task (async_func())
await task
asyncio.run(main())
view raw async_task.py hosted with ❤ by GitHub

Output

Velotio ...
... Blog!

3 Event Loops

This mechanism runs coroutines until they complete. You can imagine it as while(True) loop that monitors coroutine, taking feedback on what’s idle, and looking around for things that can be executed in the meantime.

It can wake up an idle coroutine when whatever that coroutine is waiting on becomes available.

Only one event loop can run at a time in Python.

Example:

In the snippet below, we are creating three tasks and then appending them in a list and executing all tasks asynchronously using get_event_loop, create_task and the await function of the asyncio library.

import asyncio
async def async_func(task_no):
print(f'{task_no} :Velotio ...')
await asyncio.sleep(1)
print(f'{task_no}... Blog!')
async def main():
taskA = loop.create_task (async_func('taskA'))
taskB = loop.create_task(async_func('taskB'))
taskC = loop.create_task(async_func('taskC'))
await asyncio.wait([taskA,taskB,taskC])
if __name__ == "__main__":
try:
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
except :
pass
view raw async_loop.py hosted with ❤ by GitHub

Output

taskA :Velotio ...
taskB :Velotio ...
taskC :Velotio ...
taskA... Blog!
taskB... Blog!
taskC... Blog!

Future

A future is a special, low-level available object that represents an eventual result of an asynchronous operation.

When a Future object is awaited, the co-routine will wait until the Future is resolved in some other place.

We will look into the sample code for Future objects in the next section.

A Comparison Between Multithreading and Async IO

Before we get to Async IO, let’s use multithreading as a benchmark and then compare them to see which is more efficient.

For this benchmark, we will be fetching data from a sample URL (the Velotio Career webpage) with different frequencies, like once, ten times, 50 times, 100 times, 500 times, respectively.

We will then compare the time taken by both of these approaches to fetch the required data.

Implementation

Code of Multithreading:

import requests
import time
from concurrent.futures import ProcessPoolExecutor
def fetch_url_data(pg_url):
try:
resp = requests.get(pg_url)
except Exception as e:
print(f"Error occured during fetch data from url{pg_url}")
else:
return resp.content
def get_all_url_data(url_list):
with ProcessPoolExecutor() as executor:
resp = executor.map(fetch_url_data, url_list)
return resp
if __name__=='__main__':
url = "https://www.velotio.com/careers"
for ntimes in [1,10,50,100,500]:
start_time = time.time()
responses = get_all_url_data([url] * ntimes)
print(f'Fetch total {ntimes} urls and process takes {time.time() - start_time} seconds')

Output

Fetch total 1 urls and process takes 1.8822264671325684 seconds
Fetch total 10 urls and process takes 2.3358211517333984 seconds
Fetch total 50 urls and process takes 8.05638575553894 seconds
Fetch total 100 urls and process takes 14.43302869796753 seconds
Fetch total 500 urls and process takes 65.25404500961304 seconds

ProcessPoolExecutor is a Python package that implements the Executor interface. The fetch_url_data is a function to fetch the data from the given URL using the requests python package, and the get_all_url_data function is used to map the fetch_url_data function to the lists of URLs.

Async IO Programming Example:

import asyncio
import time
from aiohttp import ClientSession, ClientResponseError
async def fetch_url_data(session, url):
try:
async with session.get(url, timeout=60) as response:
resp = await response.read()
except Exception as e:
print(e)
else:
return resp
return
async def fetch_async(loop, r):
url = "https://www.velotio.com/careers"
tasks = []
async with ClientSession() as session:
for i in range(r):
task = asyncio.ensure_future(fetch_url_data(session, url))
tasks.append(task)
responses = await asyncio.gather(*tasks)
return responses
if __name__ == '__main__':
for ntimes in [1, 10, 50, 100, 500]:
start_time = time.time()
loop = asyncio.get_event_loop()
future = asyncio.ensure_future(fetch_async(loop, ntimes))
loop.run_until_complete(future) #will run until it finish or get any error
responses = future.result()
print(f'Fetch total {ntimes} urls and process takes {time.time() - start_time} seconds')
view raw final_async.py hosted with ❤ by GitHub

Output

Fetch total 1 urls and process takes 1.3974951362609863 seconds
Fetch total 10 urls and process takes 1.4191942596435547 seconds
Fetch total 50 urls and process takes 2.6497368812561035 seconds
Fetch total 100 urls and process takes 4.391665458679199 seconds
Fetch total 500 urls and process takes 4.960426330566406 seconds

We need to use the get_event_loop function to create and add the tasks. For running more than one URL, we have to use ensure_future and gather function.

The fetch_async function is used to add the task in the event_loop object and the fetch_url_data function is used to read the data from the URL using the session package. The future_result method returns the response of all the tasks.

Results:

As you can see from the plot, async programming is much more efficient than multi-threading for the program above. 

The graph of the multithreading program looks linear, while the asyncio program graph is similar to logarithmic.


AsyncIO vs Multithreading program

Conclusion

As we saw in our experiment above, Async IO showed better performance with the efficient use of concurrency than multi-threading.

Async IO can be beneficial in applications that can exploit concurrency. Though, based on what kind of applications we are dealing with, it is very pragmatic to choose Async IO over other implementations.

We hope this article helped further your understanding of the async feature in Python and gave you some quick hands-on experience using the code examples shared above.