引言例子用的python3.10什么异步编程异步编程是一种处理多任务的策略,允许任务在等待某些操作(如IO操作)的完成时不阻塞程序的其他部分这样,可以在一个操作等待响应时处理其他任务,从而提高程序的整体执行效率。
什么是协程协程(Coroutine)是一种比线程更轻量级的用户态线程,它不同于普通的子程序或者说函数,子程序在调用完后就结束了,而协程可以保存自己的执行状态(比如局部变量等),然后暂停,从他停止的地方继续执行。
异步编程(协程)常见的应用场景网络编程:由于网络请求需要等待服务器响应,因此会有大量的I/O等待时间使用协程的异步编程模型可以使程序在等待一个请求的响应时处理其他请求,从而大大提高程序的吞吐量网络请求(爬虫):在爬取网站内容时,程序在下载网页时需要等待网络传输。
使用异步编程可以在等待网络传输的过程中爬取其他网页,提高爬虫的效率GUI(图形用户界面)开发:在处理用户的输入/操作过程中,我们不希望由于某些耗时的操作(比如读写文件)而卡住程序,影响用户体验此时,可以使用异步编程模型,将耗时的操作作为协程,在等待这些操作完成的过程中,GUI仍然能够对其他用户操作进行响应。
异步编程与协程的关系在Python中,协程是实现异步编程的一种方式使用async关键字定义的协程函数,以及由这些函数返回的协程对象,是编写异步Python程序的基础Python中的异步编程关键字async。
这个关键字用来声明一个函数是异步的异步函数在调用时返回一个协程(coroutine)对象,可以使用 await 关键字或 asyncio.run() 等函数来驱动这个 coroutine 对象import asyncio。
async def hello():print("Hello, World!")# 运行异步函数asyncio.run(hello())await这个关键字可用于等待 coroutine 对象,Future 对象,或任何实现了 awaitable 协议的对象。
在等待的过程中,当前的任务暂停,允许其他任务运行coroutine对象:使用async def 定义的函数Future对象:使用asyncio.gather()awaitable协议对象:某个类自定义了异步迭代器,这个类就是awaitable协议对象。
async for的例子中会讲到异步迭代器关于Future对象和awaitable协议对象后面例子会用到import asyncioasync def task1():print("Task 1 开始"。
) await asyncio.sleep(2) # 模拟耗时2秒的操作print("Task 1 结束")async def task2():print("我是Task 2")async def main():
# 使用 asyncio.gather 运行两个任务 await asyncio.gather(task1(), task2())if __name__ == __main__: asyncio.run(main())
当在task1中遇到了await语句并开始等待时(例如等待某个IO操作完成),那么事件循环就会挂起task1并开始执行task2换言之,就是task2被认为是"其他任务"因此,"其他任务"指的是所有在同一事件循环中且与当前等待的协程不同的协程。
async with这个语句被用于异步上下文管理器,也就是实现了__aenter__和__aexit__的对象在进入到async with块之前,会先调用__aenter__,离开时则调用__aexit__,这就允许我们在处理完相应的资源之后确保他们被正确地关闭或清理。
import asyncioclass MyAsyncContextManager: async def __aenter__(self):print("开始异步操作")return self async def __aexit__(self, exc_type, exc_val, exc_tb):
""" :param exc_type: 如果 with 块内发生了异常,exc_type 将包含异常的类型如果 with 块内没有异常,exc_type 将为 None :param exc_val: 如果 with 块内发生了异常,exc_val 将包含异常的值。
如果 with 块内没有异常,exc_val 将为 None :param exc_tb: 如果 with 块内发生了异常,exc_tb 将包含异常的追踪信息(traceback)如果 with 块内没有异常,exc_tb 将为 None。
:return: """print("结束异步操作")# __aenter__方法会在进入async with语句后被调用,返回的对象会被as子句接收# __aexit__方法会在离开async with语句后被调用,无论是否有异常发生。
async def main(): async with MyAsyncContextManager() as f:print("在异步上下文管理器内部执行操作...")if __name__ ==
__main__: asyncio.run(main())
![](http://ldjg88.com/zb_users/upload/2024/05/20240515072126171572888682188.png)
async forasync for通常与异步迭代器一起使用异步迭代器就是一个定义了__aiter__方法的对象这个方法需要返回一个异步迭代器对象,而这个异步迭代器对象需要实现__anext__方法__anext__ 方法在每次循环时都会被调用,并返回一个 Awaitable 对象,当这个对象执行完毕后,产生一个数据。
而“异步生成器”则是一种特殊的异步迭代器,它使用 async def 定义函数,并在函数内部使用 yield 来返回数据异步生成器不需要手动实现__aiter__ 和__anext__方法import asyncio。
class MyAsyncIterator: def __init__(self, data):""" 初始化函数 接受一个可迭代对象""" self.data = data# 初始化索引,指向迭代器的起始位置
self.index = 0 def __aiter__(self):# 返回迭代器自身return self# 由于__aiter__返回的是异步迭代器对象,所以__anext__要用异步定义
async def __anext__(self):if self.index >= len(self.data): raise StopAsyncIteration value = self.data[self.index]
self.index += 1return valueasync def main(): num = MyAsyncIterator([1, 2, 3, 4, 5]) async
for i in num:print(i)# 模拟异步操作 await asyncio.sleep(1)if __name__ == __main__: asyncio.run(main())
# 间隔1秒输出1,2,3,4,5yield实现异步迭代器用async def声明并在函数主体内使用yield的方式,事实上就是一种生成器的形式来实现异步迭代器下图来源:流畅的Python(第2版)
![](http://ldjg88.com/zb_users/upload/2024/05/20240515072126171572888632586.png)
pip install aiofilesimport aiofilesimport asyncioasync def async_file_reader(file_path): async with aiofiles.open(file_path,
r, encoding=utf-8) as f: async for line in f: yield lineasync def main(file_path): async
for line in async_file_reader(file_path):print(line)if __name__ == __main__: asyncio.run(main("test.txt"
))异步(协程)和多线程的耗时对比# 先安装异步请求库pip install aiohttpimport requestsimport aiohttpimport threadingimport asyncio
import time# 准备10个urlurls = [https://www.baidu.com/for _ in range(10)]def fetch_url(url): requests.get(url)
# 使用多线程10次请求def threading_request(): start_time = time.time() threads = []for url in urls: thread = threading.Thread(target=fetch_url, args=(url,))
thread.start() threads.append(thread)for thread in threads: thread.join()print(f
使用requests多线程10次请求耗时: {time.time() - start_time:.2f}s)# 使用aiohttp进行10次并发异步请求async def aiohttp_request(url):
async with aiohttp.ClientSession() as session: async with session.get(url) as resp:return resp.status
async def async_main(): start_time = time.time() tasks = [aiohttp_request(url) for url in urls]
# 使用 asyncio.gather() 同时运行多个协程任务(tasks),等待它们全部完成# tasks 是一个包含多个协程对象的列表,使用 *tasks 展开成函数参数# await 会暂停当前协程,等待所有任务完成后继续执行。
await asyncio.gather(*tasks)print(f使用aiohttp异步10次请求耗时: {time.time() - start_time:.2f}s)if __name__ ==
__main__: threading_request() asyncio.run(async_main())
![](http://ldjg88.com/zb_users/upload/2024/05/20240515072127171572888723151.png)
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。