asyncio is a library to write concurrent code using the aysnc/await syntax.
asyncio is used as a foundation for multiple Python asynchronous frameworks that provide high-performance network and web-servers, database connection libraries, distributed task queues, etc.
asyncio is often a perfect fit for IO-bound and high-level structured network code.
asyncio provides a set of high-level APIs to:
- run Python coroutines concurrently and have full control over their execution;
- perform network IO and IPC
- control subproceses
- distribute tasks via queues
- synchronize concurrent code
Coroutines
Generators are for one-way communication. We can retrieve information from a generator using next, but cannot interact with it. A number of years ago, Python introduced the send method for generators, and a modification of how yield can be used. Consider:
def myfunc():
x = ''
while True:
print(f"Yielding x ({x}) and waiting ...")
x = yield x
if x is None:
break
print(f"Got x ({x}). Doubling.")
x = x * 2The yield is on the right side of an assignment statement. This means that yield must be providing a value to the generator. From the send method, which can be invoked in place of the next function. The send method works just like next, except that you can pass any Python data structure you want into the generator. And whatever you send is then assigned to x. As the right side of assignment always executes before the left side, the generator goes to sleep after returning the right side. When it wakes up, the first thing that happens is that the sent value is assigned to x.
This is pretty neat: Our coroutine hangs around, waiting for us to give it a number to dial.
So, where do you use a generator-based coroutine? How can you think about it?
- Think of it as in-program microservice. A nanoservice, if you will.
Examples:
- Want to communicate with database? Use a coroutine, whose local variables will stick around across queries, and can thus remain connected without using lots of ugly global variables. Send your SQL queries to the coroutine, and get back the query results.
- Want to communicate with external network service, such as a stock-market quote system? Use a coroutine, to which you can send a tuple of symbol and date, and from which you’ll receive the latest information in a dictionary.
def bad__chatbot():
answers = ["We don't do that",
"We will get back to you right away",
"Your call is very important to us",
"Sorry, my manager is unavailable"]
yield "Can I help you?"
s = ''
while True:
if s is None:
break
s = yield random.choice(answers)async def coroutines:
Coroutines are a more generalized form of subroutines. Subroutines are entered at one point and exited at another point. Coroutines can be entered, exited, and resumed at many different points. They can be implemented with the async def statement.
async def read_data(db):
passKey properties of coroutines:
async def’s are always coroutines even if they do not containawaitexpressions.- It is a
SyntaxErrorto haveyieldoryield fromexpression in anasyncfunction.
The await expression is used to obtain a result of coroutine execution:
async def read_data(db):
data = await db.fetch('SELECT ...')
...await, similarly to yield from, suspends execution of read_data coroutine until db.fetch awaitable completes and returns the result data.
We say that an object is an awaitable if it can be used in an await expression. Many asyncio APIs are designed to accept awaitables. There are three main types of awaitable objects: coroutines, Tasks, and Futures.
To actually run a coroutine, asyncio provides the following mechanisms:
- The
asyncio.run()function to run the top-level entry pointmain()function. - Awaiting on a coroutine.
The event loop is the core of the every asyncio application. Event loops run asynchronous tasks and callbacks, perform network IO operations, and run subprocesses. Application developers should typically use the high level asyncio functions, such as asyncio.run(), and should rarely need to reference the loop object or call its methods.
import asyncio
import datetime
async def display_date():
end_time = datetime.datetime.now() + datetime.timedelta(seconds=5)
while True:
now = datetime.datetime.now()
print(now)
if now >= end_time:
break
await asyncio.sleep(1)
asyncio.run(display_date())Running tasks concurrently:
awaitable asyncio.gather(*aws, return_exceptions=False)Run awaitable objects in the aws sequence concurrently. If any awaitable in aws is a coroutine, it is automatically scheduled as a Task. If the awaitables are completed successfully, the result is an aggregate list of returned values.The order of result values corresponds to the order of awaitables in aws.