Asyncio Documentation Documentation: Release 0.0
Asyncio Documentation Documentation: Release 0.0
Documentation
Release 0.0
Victor Stinner
Mike Müller
5 See also 31
6 Contributing 33
6.1 Asyncio documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.2 Notes to writers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.3 Ideas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.4 How to install Sphinx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.5 How to build the documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6.6 See also . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
i
ii
CHAPTER 1
asyncio is a library to write asynchronous applications. It is the most efficient way to implement a network server
having to handle many concurrent users.
Or Why should I bother with all these extra annoying async and await keywords?.
In short, asyncio adopted a radically different solution for race conditions.
Parallel computing using threads is hard because of race conditions. Gevent and eventlet have a similar issue using
“green” (lightweight) threads.
Code written with asyncio is less error-prone: by just looking at the code, it is possible to identify which parts of the
code are under our controls and where the event loop takes over the control flow and is able to run other tasks when
our task is waiting for something.
gevent and eventlet are designed to hide the asynchronous programming. For non-expert, and sometimes even for
experts, it is really hard to guess where the event loop is allowed to suspend the task and run other tasks in background.
It is even worse. A modification in a third party library can change the behaviour of our code, introduce a new point
where the task is suspended.
For an example, see the “Ca(sh|che Coherent) Money” section of the Unyielding article (by Glyph, February, 2014).
1
Asyncio Documentation Documentation, Release 0.0
This documentation is written for Python 3.5 to avail of the new async and await keywords.
If you have Python 3.5 installed you only need to install aiohttp:
If you don’t have Python 3.5 installed yet, you have several options to install it.
Windows:
• Install aiohttp:
Platform specific
• Windows: The easiest way to use Python 3.5 would be to use a package manager such as conda. See the
installation instructions above.
• Mac OS X: Install Homebrew and then type brew install python3
• Linux: Ubuntu 16.04+ and Arch linux ship with Python 3.5 included. If you don’t have Python 3.5+ on your
computer, you can compile it or use Pythonz.
Note: Depending on your platform, the Python 3 interpreter could be invoked by python instead. This is the case
for conda on Windows for example.
This is a series of examples showing the basics of how to write coroutines and schedule them in the asyncio event
loop.
import asyncio
loop = asyncio.get_event_loop()
loop.run_until_complete(say('hello world', 1))
loop.close()
This second example shows how you can schedule multiple coroutines in the event loop, and then run the event loop.
Notice that this example will print second_hello before first_hello, as the first task scheduled waits longer
that the second one before printing.
Also note that this example will never terminate, as the loop is asked to run_forever.
import asyncio
loop = asyncio.get_event_loop()
loop.run_forever()
loop.close()
This third example adds another task that will stop the event loop before all scheduled tasks could execute, which
results in a warning.
import asyncio
loop = asyncio.get_event_loop()
loop.run_forever()
loop.close()
Warning:
Example illustrating how to schedule two coroutines to run concurrently. They run for ten minutes, during which the
first coroutine is scheduled to run every second, while the second is scheduled to run every minute.
The function asyncio.gather is used to schedule both coroutines at once.
import asyncio
loop = asyncio.get_event_loop()
loop.run_until_complete(
asyncio.gather(print_every_second(),
print_every_minute())
)
loop.close()
import asyncio
import aiohttp
loop = asyncio.get_event_loop()
with aiohttp.ClientSession(loop=loop) as session:
content = loop.run_until_complete(
fetch_page(session, 'http://python.org'))
print(content)
loop.close()
Random notes about tuning asyncio for performance. Performance means two different terms which might be incom-
patible:
• Number of concurrent requests per second
• Request latency in seconds: min/average/max time to complete a request
Because of its GIL, CPython is basically only able to use 1 CPU. To increase the number of concurrent requests, one
solution is to spawn multiple worker processes. See for example:
• Gunicorn
• API-Hour
aiohttp uses set_writer_buffer_limits(0) for backpressure support and implemented their own buffering,
see:
• aio-libs/aiohttp#1369
• Some thoughts on asynchronous API design in a post-async/await world (November, 2016) by Nathaniel J.
Smith
1.6.3 TCP_NODELAY
Since Python 3.6, asyncio now sets the TCP_NODELAY option on newly created sockets: disable the Nagle algorithm
for send coalescing. Disable segment buffering so data can be sent out to peer as quickly as possible, so this is typically
used to improve network utilisation.
See Nagle’s algorithm.
1.6.4 TCP_QUICKACK
The Twisted project is probably one of the oldest libraries that supports asynchronous programming in Python. It has
been used by many programmers to develop a variety of applications. It supports many network protocols and can
be used for many different types of network programming. In fact, asyncio was heavily inspired by Twisted. The
expertise of several Twisted developers had been incorporated in asyncio. Soon, there will be a version of Twisted that
is based on asyncio.
Twisted asyncio
Deferred asyncio.Future
deferToThread(func) loop.run_in_executor(None, func)
@inlineCallbacks async def
reactor.run() loop.run_forever()
This small example shows two equivalent programs, one implemented in Twisted and one in asyncio.
Twisted asyncio
Basic Twisted example using deferred: Similar example written using asyncio:
from twisted.internet import defer import asyncio
from twisted.internet import reactor
reactor.stop()
d = defer.Deferred()
d.addCallback(step1)
d.addCallback(step2)
d.callback(5)
reactor.run()
• python-tulip Google Group: historical name of the official asyncio mailing list
1.8.2 StackOverflow
There is an python-asyncio tag on StackOverflow where you can read new questions but also read answers of previous
questions.
1.8.3 IRC
import asyncio
9
Asyncio Documentation Documentation, Release 0.0
import asyncio
loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_echo, '127.0.0.1', 8888, loop=loop)
server = loop.run_until_complete(coro)
2.2 Threads
import asyncio
def compute_pi(digits):
# implementation
return 3.14
loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
loop.close()
See also:
• run_in_executor() documentation
2.3 Subprocess
A simple example to run commands in a subprocess using asyncio.create_subprocess_exec and get the output using
process.communicate:
import asyncio
loop = asyncio.get_event_loop()
# Gather uname and date commands
commands = asyncio.gather(run_command('uname'), run_command('date'))
# Run the commands
uname, date = loop.run_until_complete(commands)
# Print a report
print('uname: {}, date: {}'.format(uname, date))
loop.close()
A simple example to communicate with an echo subprocess using process.stdin and process.stdout:
import asyncio
2.3. Subprocess 11
Asyncio Documentation Documentation, Release 0.0
print('Received {!r}'.format(reply))
# Stop the subprocess
process.terminate()
code = await process.wait()
print('Terminated with code {}'.format(code))
loop = asyncio.get_event_loop()
loop.run_until_complete(echo('hello!'))
loop.close()
2.4 Producer/consumer
import asyncio
import random
loop = asyncio.get_event_loop()
queue = asyncio.Queue(loop=loop)
producer_coro = produce(queue, 10)
consumer_coro = consume(queue)
loop.run_until_complete(asyncio.gather(producer_coro, consumer_coro))
loop.close()
import asyncio
import random
loop = asyncio.get_event_loop()
loop.run_until_complete(run(10))
loop.close()
2.4. Producer/consumer 13
Asyncio Documentation Documentation, Release 0.0
Web scraping means downloading multiple web pages, often from different servers. Typically, there is a considerable
waiting time between sending a request and receiving the answer. Using a client that always waits for the server to
answer before sending the next request, can lead to spending most of time waiting. Here asyncio can help to send
many requests without waiting for a response and collecting the answers later. The following examples show how a
synchronous client spends most of the time waiting and how to use asyncio to write asynchronous client that can
handle many requests concurrently.
This is a very simple web server. (See below for the code.) Its only purpose is to wait for a given amount of time. Test
it by running it from the command line:
$ python simple_server.py
http://localhost:8000/
http://localhost:8000/2.5
15
Asyncio Documentation Documentation, Release 0.0
After pressing enter, it will take 2.5 seconds until you see this response:
Use different numbers and see how long it takes until the server responds.
The full implementation looks like this:
# file: simple_server.py
"""Simple HTTP server with GET that waits for given seconds.
"""
ENCODING = 'utf-8'
class MyRequestHandler(BaseHTTPRequestHandler):
"""Very simple request handler. Only supports GET.
"""
def run(server_class=ThreadingHTTPServer,
handler_class=MyRequestHandler,
port=8000):
"""Run the simple server on given port.
"""
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print('Serving from port {} ...'.format(port))
httpd.serve_forever()
if __name__ == '__main__':
run()
Let’s have a look into the details. This provides a simple multi-threaded web server:
It uses multiple inheritance. The mix-in class ThreadingMixIn provides the multi-threading support and the class
HTTPServer a basic HTTP server.
The request handler only has a GET method:
class MyRequestHandler(BaseHTTPRequestHandler):
"""Very simple request handler. Only supports GET.
"""
It takes the last entry in the paths with self.path[1:], i.e. our 2.5, and tries to convert it into a floating point
number. This will be the time the function is going to sleep, using time.sleep(). This means waiting 2.5 seconds
until it answers. The rest of the method contains the HTTP header and message.
ENCODING = 'ISO-8859-1'
def get_encoding(http_response):
"""Find out encoding.
"""
content_type = http_response.getheader('Content-type')
for entry in content_type.split(';'):
if entry.strip().startswith('charset'):
return entry.split('=')[1].strip()
return ENCODING
if __name__ == '__main__':
def main():
"""Test it.
"""
pages = get_multiple_pages(host='http://localhost', port='8000',
waits=[1, 5, 3, 2])
for page in pages:
print(page)
main()
def get_encoding(http_response):
"""Find out encoding.
"""
content_type = http_response.getheader('Content-type')
for entry in content_type.split(';'):
if entry.strip().startswith('charset'):
return entry.split('=')[1].strip()
return ENCODING
We just iterate over the waiting times and call get_page() for all of them. The function time.perf_counter()
provides a time stamp. Taking two time stamps a different points in time and calculating their difference provides the
elapsed run time.
Finally, we can run our client:
$ python synchronous_client.py
Because we wait for each call to get_page() to complete, we need to wait about 11 seconds. That is the sum of all
waiting times. Let’s see if we can do it any better going asynchronously.
This module contains a functions that reads a page asynchronously, using the new Python 3.5 keywords async and
await:
# file: async_page.py
import asyncio
ENCODING = 'ISO-8859-1'
def get_encoding(header):
"""Find out encoding.
"""
for line in header:
if line.lstrip().startswith('Content-type'):
for entry in line.split(';'):
if entry.strip().startswith('charset'):
return entry.split('=')[1].strip()
return ENCODING
if not line.strip():
break
header.append(line)
encoding = get_encoding(header)
async for raw_line in reader:
line = raw_line.decode(encoding).strip()
msg_lines.append(line)
writer.close()
return '\n'.join(msg_lines)
As with the synchronous example, finding out the encoding of the page is a good idea. This function helps here by
going through the lines of the HTTP header, which it gets as an argument, searching for charset and returning its
value if found. Again, the default encoding is ISO-8859-1:
def get_encoding(header):
"""Find out encoding.
"""
for line in header:
if line.lstrip().startswith('Content-type'):
for entry in line.split(';'):
if entry.strip().startswith('charset'):
return entry.split('=')[1].strip()
return ENCODING
The next function is way more interesting because it actually works asynchronously:
The function asyncio.open_connection() opens a connection to the given URL. It returns a coroutine. Using
await, which had to be yield from in Python versions prior to 3.5, it yields an instance of a StreamReader
and one of a StreamWriter. These only work within the event loop.
Now, we can send a GET request, suppling our waiting time by writing to the StreamWriter instance writer.
The request has to be in bytes. Therefore, we need to convert our strings in to bytestrings.
Next, we read header and message from the reader, which is a StreamReader instance. We need to iterate over the
reader by using a special or loop for asyncio:
Header and message are dived by an empty line. We just stop the iteration as soon as we found an empty line. Handing
the header over too get_encoding() provides the encoding of the retrieved page. The .decode() method uses
this encoding to convert the read bytes into strings. After closing the writer, we can return the message lines joined by
newline characters.
This is our first approach retrieving multiple pages, using our asynchronous get_page():
import asyncio
from contextlib import closing
import time
if __name__ == '__main__':
def main():
"""Test it.
"""
pages = get_multiple_pages(host='localhost', port='8000',
waits=[1, 5, 3, 2])
for page in pages:
print(page)
main()
The interesting things happen in a few lines in get_multiple_pages() (the rest of this function just measures
the run time and displays it):
The closing from the standard library module contextlib starts the event loop within a context and closes the
loop when leaving the context:
loop = asyncio.get_event_loop():
try:
<body>
finally:
loop.close()
We call get_page() for each page in a loop. Here we decide to wrap each call in loop.
run_until_complete():
This means, we wait until each pages has been retrieved before asking for the next. Let’s run it from the command-line
to see what happens:
$ async_client_blocking.py
It took 11.06 seconds for a total waiting time of 11.00.
Waited for 1.00 seconds.
That's all.
Waited for 5.00 seconds.
That's all.
Waited for 3.00 seconds.
That's all.
Waited for 2.00 seconds.
That's all.
So it still takes about eleven seconds in total. We made it more complex and did not improve speed. Let’s see if we
can do better.
We want to take advantage of the asynchronous nature of get_page() and save time. We modify our client to use a
list with four instances of a task. This allows us to send out requests for all pages we want to retrieve without waiting
for the answer before asking for the next page:
import asyncio
if __name__ == '__main__':
def main():
"""Test it.
"""
pages = get_multiple_pages(host='localhost', port='8000',
waits=[1, 5, 3, 2])
for page in pages:
print(page)
main()
We append all return values of get_page() to our lits of tasks. This allows us to send out all request, in our case
four, without waiting for the answers. After sending all of them, we wait for the answers, using:
loop.run_until_complete(asyncio.gather(*tasks))
We used loop.run_until_complete() already for each call to get_page() in the previous section. The
difference here is the use of asyncio.gather() that is called with all our tasks in the list tasks as arguments.
The asyncio.gather(*tasks) means for our example with four list entries:
asyncio.gather(tasks[0], tasks[1], tasks[2], tasks[3])
$ async_client_nonblocking.py
It took 5.08 seconds for a total waiting time of 11.00.
Waited for 1.00 seconds.
That's all.
Waited for 5.00 seconds.
That's all.
Waited for 3.00 seconds.
That's all.
Waited for 2.00 seconds.
That's all.
Yes! It works. The total run time is about five seconds. This is the run time for the longest wait. Now, we don’t have
to wait for the sum of waits but rather for max(waits).
We did quite a bit of work, sending a request and scanning an answer, including finding out the encoding. There should
be a shorter way as these steps seem to be always necessary for getting the page content with the right encoding.
Therefore, in the next section, we will have a look at high-level library aiohttp that can help to make our code
shorter.
Exercise
Add more waiting times to the list waits and see how this impacts the run times of the blocking and the non-blocking
implementation. Try (positive) numbers that are all less than five. Then try numbers greater than five.
The library aiohttp allows to write HTTP client and server applications, using a high-level approach. Install with:
import asyncio
from contextlib import closing
import time
import aiohttp
if __name__ == '__main__':
def main():
"""Test it.
"""
pages = get_multiple_pages(host='http://localhost', port='8000',
waits=[1, 5, 3, 2])
for page in pages:
print(page)
main()
The function to get one page is asynchronous, because of the async def:
The arguments are the same as those for the previous function to retrieve one page plus the additional argument
session. The first task is to construct the full URL as a string from the given host, port, and the desired waiting
time.
We use a timeout of 10 seconds. If it takes longer than the given time to retrieve a page, the programm throws a
TimeoutError. Therefore, to make this more robust, you might want to catch this error and handle it appropriately.
The async with provides a context manager that gives us a response. After checking the status being 200, which
means that all is alright, we need to await again to return the body of the page, using the method text() on the
response.
This is the interesting part of get_multiple_pages():
It is very similar to the code in the example of the time-saving implementation with asyncio. The only difference is
the opened client session and handing over this session to fetch_page() as the first argument.
Finally, we run this program:
$ python aiohttp_client.py
It took 5.04 seconds for a total waiting time of 11.00.
Waited for 1.00 seconds.
That's all.
It also takes about five seconds and gives the same output as our version before. But the implementation for getting a
single page is much simpler and takes care of the encoding and other aspects not mentioned here.
4.1 Glossary
coroutine A coroutine is a piece of code that can be paused and resumed. In contrast to threads which are preemp-
tively multitasked by the operating system, coroutines multitask cooperatively. I.e. they choose when to pause
(or to use terminology for coroutines before 3.4 - yield) execution. They can also execute other coroutines.
event loop The event loop is the central execution device to launch execution of coroutines and handle I/O (Network,
sub-processes. . . )
future It’s like a mailbox where you can subscribe to receive a result when it will be done. More details in official
documentation
task It represents the execution of a coroutine and take care the result in a future. More details in official documen-
tation
29
Asyncio Documentation Documentation, Release 0.0
See also
• asyncio wiki
• asyncio Reference Documentation.
• A Web Crawler With asyncio Coroutines by A. Jesse Jiryu Davis and Guido van Rossum
• Writing Redis in Python with asyncio: Part 1 by James Saryerwinnie
31
Asyncio Documentation Documentation, Release 0.0
Contributing
Tutorials should use Python 3.5 async and await keywords rather than @asyncio.coroutine and yield
from.
6.3 Ideas
• Advanced section:
– protocols and transports: as least point to good implementations
– explain how to test asyncio applications. Twisted documentation example
Firstly, you need to install the Sphinx tool using the Linux package manager like apt-get or dnf for example.
But if you want to install it via pip , you can create a virtual environment with the venv module of Python 3
33
Asyncio Documentation Documentation, Release 0.0
Once you have installed Sphinx, you can build the documentation.
Install Sphinx using the Linux package manager like apt-get or dnf for example. Then build the documentation using:
make html
• https://github.com/python/asyncio
• http://krondo.com/an-introduction-to-asynchronous-programming-and-twisted/
• https://curio.readthedocs.io/en/latest/tutorial.html
34 Chapter 6. Contributing
Index
C
coroutine, 29
E
event loop, 29
F
future, 29
T
task, 29
35