编译自 https://tryexceptpass.org/article/asyncio-in-37/
asyncio相关模块已经成为Python很核心的一部分,aio-libs一直在持续的发展中,例如aiohttp、aiopg等库已经可以初步的在生产环境使用了。在Python3.7中,引入了一系列的与asyncio相关变化,这些变化聚焦在代码质量,让开发者尽量地减少工作量和获得更好的性能体验,主要内容包括了<新的保留字>、<环境变量>、<新的asyncio.run()函数>、<更简单的任务管理、时间循环管理>、<回调更新>、<异步的上下文管理器>等。
Python3.7中 async和await 成为了关键字,这也意味着async和await不能成为变量名字了。如果在之前的代码里包含了async和await的变量命令,那么迁移到3.7就需要改变命名了。
# This is a syntax error
def do_some_long_op(async=True):
...
# This is not
def do_some_long_op(async_=True):
...
do_something()
# This is also a syntax error
async = 'this work could be asynchronous'
# This is not
async_ = 'this work could be asynchronous'
do some_other_thing()
上下文变量是在3.7新引入的特性,类似于全局变量,只不过这个全局变量针对于所有任务。下面代码的含义是一个异步服务在新的客户端发起连接时执行handle_request()函数,此时会设置client_addr_var变量,这样的话就不需要传递变量给render_goodbye()函数,而可以直接访问ContextVar类,获得变量。这个包存在的意义在于保存异步环境下的各种状态,简化参数传递等操作。
import asyncio
import contextvars
client_addr_var = contextvars.ContextVar('client_addr')
def render_goodbye():
# The address of the currently handled client can be accessed
# without passing it explicitly to this function.
client_addr = client_addr_var.get()
return f'Good bye, client @ {client_addr}\n'.encode()
async def handle_request(reader, writer):
addr = writer.transport.get_extra_info('socket').getpeername()
client_addr_var.set(addr)
# In any code that we call is now possible to get
# client's address by calling 'client_addr_var.get()'.
while True:
line = await reader.readline()
...
writer.write(render_goodbye())
writer.close()
async def main():
srv = await asyncio.start_server(handle_request, '127.0.0.1', 8081)
async with srv:
await srv.serve_forever()
asyncio.run(main())
这个函数旨在简化get_event_loop、run_until_complete、close的模板代码。
async def some_async_task():
...
# Before Python 3.7
loop = asyncio.get_event_loop()
loop.run_until_complete(some_async_task())
loop.close()
# After Python 3.7
asyncio.run(some_async_task())
任务管理牵扯到任务创建、维护和关闭,最常调用的current_task()和all_tasks()两个函数从asyncio.Task移出(相关接口被设置废弃状态)。
try:
loop.run_forever()
except KeyboardInterrupt:
# Canceling pending tasks and stopping the loop
# Previous to Python 3.7
asyncio.gather(*asyncio.Task.all_tasks()).cancel()
# After the changes in 3.7
asyncio.gather(*asyncio.all_tasks()).cancel()
之前的事件循环只有asyncio.get_event_loop()一个函数,但是3.7新加的asyncio.get_running_loop()会获得一个正在运行的事件循环(如果不存在就会抛出RuntimeError错误),这个主要是为了方便解耦各个模块之间的事件循环。
当使用call_soon()或者是call_soon_threadsafe()函数时一般而言只是拿到Handle对象,而无法确定此次回调是否被取消,3.7新加入了Handle.cancelled()方法以确定此次回调是否已经取消。
这个和Python之前的上下文管理器类似,就是with语法。只不过之前的异步上下文需要实现标准的aenter__() or __aexit()方法,现在可以和非异步环境下的contextmanager()装饰器一样,使用yield语法。
from contextlib import asynccontextmanager
@asynccontextmanager()
async def login(username, password):
# Wait for the login to complete and return the token
token = await _login_to_web_api(username, password)
try:
# Execute the context block
yield token
finally:
# Logout
await _logout_from_web_api(token)
async def list_resources():
async with login(username, password) as token:
# We are now logged in and have a valid token
return await list_resources(token)
asyncio.get_event_loop()、asyncio.gather()、asyncio.sleep()和Future的回调管理都有不错的性能提升。主要原因还是在于有些函数用C重写了。
其他相关更新省略了,因为如果不是类似于开发库包的作者,应该都不会用到,上面的就是日常可能会用到的更新。由于这些更新异步编程在Python3.7中获得了极好的体验提升,正如Python之禅所述:
Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex.