随着现代网络应用的日益复杂,处理大量并发I/O操作成为了一个挑战。python从3.4版本开始在标准库中增加了 asyncio 模块,就是为了解决这一问题。它提供了编写简洁、高效和可扩展异步代码的框架,特别适用于网络编程场景。
一、一些基本概念
事件循环(Event Loop): asyncio的核心,负责管理和调度不同任务的执行、处理事件以及分配资源。
协程(Coroutine): 使用async/await语法定义的函数,可以在特定点暂停和恢复执行,从而允许其他操作在暂停期间运行。
Future : 代表未来结果的对象,通常由低层异步回调产生。
Task: 将协程包装为Future对象的异步执行单元,由事件循环进行调度。
二、Hello world
这里先从一个简单的例子入手,比如我现在有点闪电附体,本来正常的一句”Hello world" 需要5秒钟才能说完,这时候,你不需要一直等着,你做其他事情,可以听歌、玩游戏等等,等到我说完了world,再回来跟我交流,我们模拟一下这个过程:
import asyncio
async def say_hello_async():
print("Hello-start")
await asyncio.sleep(5)
print("world-end")
async def do_something_else(thing):
print("你现在可以{}".format(thing))
await asyncio.sleep(1)
print("结束{}".format(thing))
async def main():
await asyncio.gather(
say_hello_async(),
do_something_else("听歌"),
do_something_else("看电视"),
do_something_else("玩手机"),
)
asyncio.run(main())
运行之后的结果:
PS C:\coding\aNewPy> & C:/ProgramData/anaconda3/python.exe c:/coding/aNewPy/asyncioStudy.py
Hello-start
你现在可以听歌
你现在可以看电视
你现在可以玩手机
结束听歌
结束看电视
结束玩手机
world-end
可以看到在say_hello_async这个函数执行期间,程序还可以去执行其他任务,这就是异步的效果。
三、async & await
async & await 关键字在Python3.5版本中正式引入,await能够暂停一个async函数的执行,直到可等待对象(如协程、任务、期货或I/O操作)完成,从而让出执行权,使其他任务得以在此期间运行。像上个例子中的say_hello_async函数其实还没执行完,但是先让出CPU,等到时间了再回来执行这个函数。
四、Future & Task
Future
是一个低级别的可等待对象(awaitable
),它代表了异步操作的结果。Future
对象可以用来传递在事件循环中不同部分之间的消息,或者用来协调事件循环中的不同部分。
以下是Future
的一些关键特点:
-
代表潜在的值:
Future
对象代表了将来某个时刻可能会有的值。它可以在异步操作完成时存储一个结果,或者在操作失败时存储一个异常。 -
可等待:
Future
对象是可等待的,这意味着你可以在async
函数中使用await
关键字来等待Future
对象的结果。 -
可以取消:
Future
对象可以被取消。如果一个Future
被取消,它将不再等待结果,而是立即抛出一个CancelledError
。 -
添加完成回调:你可以为
Future
对象添加回调函数,当Future
对象完成时,这些回调函数将被调用。 -
查询状态:你可以检查
Future
对象是否已经完成,是否被取消,或者是否还在等待。
Future
对象通常由事件循环创建,并且在后台的异步操作中使用。例如,当你在异步程序中发起一个网络请求时,可能会返回一个Future
对象,你可以在该Future
对象上使用await
来等待请求完成并获取结果。
Task
是 asyncio 库中用于并发执行协程的一种对象。它允许你在事件循环中调度协程的执行,并且可以追踪协程的运行和完成状态,以便未来获取协程的结果。
在asyncio
中,Task
是Future
的一个子类,它专门用于封装协程对象并在事件循环中执行它们。Task
对象继承了Future
的所有特性,并且添加了与协程执行相关的额外功能,比如取消协程执行的能力。
在实际编程中,你通常不需要直接创建Future
对象,因为asyncio
提供了更高级别的API,如asyncio.create_task()
,它会自动为你创建和管理Task
对象。然而,理解Future
的概念对于深入理解asyncio
的工作原理是非常有帮助的。
创建 Task
的主要方法是使用 asyncio.create_task()
函数,这个函数接受一个协程对象作为参数,并返回一个 Task
对象。这个 Task
对象可以被事件循环调度执行。在Python 3.7及以后的版本中,asyncio.create_task()
是推荐的方式来创建任务。
一旦 Task
被创建,它不会立即开始执行。它需要被添加到事件循环中,并且通常通过 await
一个或多个 Task
对象来启动它们的执行。你可以使用 await asyncio.wait()
或 await asyncio.gather()
来等待一个或多个 Task
对象的完成。
Task
对象提供了多种方法来管理任务的生命周期,包括取消任务、检查任务是否完成、获取任务的结果等。例如,task.cancel()
方法可以请求取消一个正在运行的任务,而 task.cancelled()
方法可以用来检查任务是否被成功取消。
在实际应用中,Task
对象常用于实现并发执行多个协程,例如在网络应用中并发处理多个网络请求。通过 asyncio.gather()
可以同时等待多个 Task
对象的完成,并收集它们的结果。
示例用法:
import asyncio
async def func1():
print(1)
await asyncio.sleep(2)
print(2)
async def func2():
print(3)
await asyncio.sleep(2)
print(4)
tasks = [
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
运行结果:
1
3
2
4
五、抓取网页(并发I/O任务)
抓取网页是展示异步编程能力的一个经典例子。比如我们正常写一个抓取网页的程序:
import requests
import time
start_time = time.time()
def fetch(url):
return requests.get(url).text
page1 = fetch(" https://www.baidu.com")
page2 = fetch("https://www.126.com")
print("完成! 耗时为{}秒".format(time.time() - start_time))
运行结果:
PS C:\coding\aNewPy> & C:/ProgramData/anaconda3/python.exe c:/coding/aNewPy/asyncioStudy2.py
完成! 耗时为1.089118480682373秒
现在我们用异步的方式改造一下:
import aiohttp
import asyncio
import time
async def fetch_async(url, session):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
page1 = asyncio.create_task(fetch_async('https://www.baidu.com', session))
page2 = asyncio.create_task(fetch_async('https://www.126.com', session))
await asyncio.gather(page1, page2)
start_time = time.time()
asyncio.run(main())
print("完成! 耗时为{}秒".format(time.time() - start_time))
运行结果:
PS C:\coding\aNewPy> & C:/ProgramData/anaconda3/python.exe c:/coding/aNewPy/asyncioStudy2.py
完成! 耗时为0.5322589874267578秒
可以看到效率有了明显提升。
六、并发读取文件(I/O任务)
有时候我们在程序中需要读取多个文件,这时候用并发的办法可以大大提高效果。
# 同步读取多个文件
def read_file_sync(filepath):
with open(filepath, 'r') as file:
return file.read()
def read_all_sync(filepaths):
return [read_file_sync(filepath) for filepath in filepaths]
filepaths = ['file1.txt', 'file2.txt']
data = read_all_sync(filepaths)
print(data)
也可以使用异步操作文件的库 aiofiles ,但这不是个标准库,需要 pip install aiofiles安装
import asyncio
import aiofiles
# 异步读取单个文件
async def read_file_async(filepath):
async with aiofiles.open(filepath, 'r') as file:
return await file.read()
async def read_all_async(filepaths):
tasks = [read_file_async(filepath) for filepath in filepaths]
return await asyncio.gather(*tasks)
# 运行异步函数
async def main():
filepaths = ['file1.txt', 'file2.txt']
data = await read_all_async(filepaths)
print(data)
asyncio.run(main())
在Python应用程序中采用asyncio可以极大地提升I/O绑定和网络驱动程序的性能和可扩展性。通过掌握事件循环、协程、Future和Task等关键概念,开发人员能够编写高效、无阻塞的代码,轻松处理大规模并发连接。