Appearance
Using asyncio
asyncio is a standard library introduced in Python 3.4 that provides built-in support for asynchronous I/O.
The programming model of asyncio is a message loop. The asyncio module implements an EventLoop internally, allowing coroutines to be thrown into the EventLoop for execution, thereby achieving asynchronous I/O.
Using the @asyncio.coroutine decorator, we can mark a generator as a coroutine type, and then use yield from
within the coroutine to call another coroutine for asynchronous operations.
To simplify and better identify asynchronous I/O, Python 3.5 introduced new syntax with async
and await
, making coroutine code more concise and readable.
Here is the Hello World code implemented with asyncio:
python
import asyncio
async def hello():
print("Hello world!")
# Asynchronous call to asyncio.sleep(1):
await asyncio.sleep(1)
print("Hello again!")
asyncio.run(hello())
The async
keyword transforms a function into a coroutine, which we then execute by passing the async function to asyncio.run()
. The execution result is as follows:
Hello!
(Waits about 1 second)
Hello again!
The hello()
function first prints "Hello world!" and then the await
syntax allows us to easily call another async function. Since asyncio.sleep()
is also an async function, the thread does not wait for asyncio.sleep()
, but instead interrupts and executes the next message loop. When asyncio.sleep()
returns, it continues to execute the next line of code.
Think of asyncio.sleep(1)
as an I/O operation that takes 1 second; during this time, the main thread does not wait but goes on to execute other async functions available in the EventLoop, achieving concurrent execution.
The previous hello()
example does not yet showcase the characteristics of concurrent execution. Let's rewrite it to allow two hello()
calls to execute concurrently:
python
# Accepting a name parameter:
async def hello(name):
# Print the name and current thread:
print("Hello %s! (%s)" % (name, threading.current_thread()))
# Asynchronous call to asyncio.sleep(1):
await asyncio.sleep(1)
print("Hello %s again! (%s)" % (name, threading.current_thread()))
return name
We can schedule multiple async functions concurrently using asyncio.gather()
:
python
async def main():
L = await asyncio.gather(hello("Bob"), hello("Alice"))
print(L)
asyncio.run(main())
The execution result is as follows:
Hello Bob! (<function current_thread at 0x10387d260>)
Hello Alice! (<function current_thread at 0x10387d260>)
(Waits about 1 second)
Hello Bob again! (<function current_thread at 0x10387d260>)
Hello Alice again! (<function current_thread at 0x10387d260>)
['Bob', 'Alice']
From the result, we can see that when executing async functions with asyncio.run()
, all functions are executed by the same thread. The two hello()
calls are executed concurrently and can return the results of the async function execution (i.e., the return values).
If we replace asyncio.sleep()
with actual I/O operations, multiple concurrent I/O operations can effectively be handled by a single thread.
Let's use asyncio's asynchronous network connections to fetch the homepage of sina, sohu, and 163 websites:
python
import asyncio
async def wget(host):
print(f"wget {host}...")
# Connect to port 80:
reader, writer = await asyncio.open_connection(host, 80)
# Send HTTP request:
header = f"GET / HTTP/1.0\r\nHost: {host}\r\n\r\n"
writer.write(header.encode("utf-8"))
await writer.drain()
# Read HTTP response:
while True:
line = await reader.readline()
if line == b"\r\n":
break
print("%s header > %s" % (host, line.decode("utf-8").rstrip()))
# Ignore the body, close the socket
writer.close()
await writer.wait_closed()
print(f"Done {host}.")
async def main():
await asyncio.gather(wget("www.sina.com.cn"), wget("www.sohu.com"), wget("www.163.com"))
asyncio.run(main())
The execution result is as follows:
wget www.sohu.com...
wget www.sina.com.cn...
wget www.163.com...
(Waits a while)
(Prints sohu's header)
www.sohu.com header > HTTP/1.1 200 OK
www.sohu.com header > Content-Type: text/html
...
(Prints sina's header)
www.sina.com.cn header > HTTP/1.1 200 OK
www.sina.com.cn header > Date: Wed, 20 May 2015 04:56:33 GMT
...
(Prints 163's header)
www.163.com header > HTTP/1.0 302 Moved Temporarily
www.163.com header > Server: Cdn Cache Server V2.0
...
It can be seen that three connections are concurrently executed by a single thread to complete the three async functions.
Summary
- asyncio provides comprehensive support for asynchronous I/O, allowing scheduling of a coroutine with
asyncio.run()
. - Within an async function, you can call another async function using
await
. This call appears to be executed serially, but is actually controlled by the internal message loop of asyncio. - Within an async function, you can concurrently execute several async functions using
await asyncio.gather()
.