Python中的asyncio异步编程旨在解决单线程程序在IO密集型任务中的性能瓶颈,通过使用异步和非阻塞机制来提高程序的执行效率。但是,在实际编程中,经常会遇到一些问题。本文将针对这些常见问题进行总结,以帮助开发者更好地理解asyncio的异步编程模式。
问题一:为什么asyncio中的回调函数不能返回值?
在使用第三方库中的回调函数时,我们通常可以在回调函数中返回需要的值,但是在asyncio中,回调函数却不能返回任何值。这是因为 await 关键字的存在。
在asyncio中,我们无法直接在回调函数中返回值,当我们想要获取协程的结果时,需要使用await来等待协程的执行结果。因此,回调函数只能异步调用协程函数,等待协程函数返回结果之后再进行异步处理。
问题二:为什么使用asyncio时会出现一些莫名其妙的错误信息?
当使用async/await关键字进行异步编程时,我们可能会遇到一些莫名其妙的错误信息,比如“async ‘xxx’ was never awaited”等。这是因为在异步编程中,当我们使用协程时,需要使用await将其挂起,等到协程运行完毕之后再继续执行后面的代码,否则程序会在未等到协程执行完毕时就直接跳出函数,导致出现错误信息。
下面示例代码中,如果不使用await,程序会在未等到get_data协程执行完毕时就直接返回,导致出现”RuntimeError: coroutine ‘get_data’ was never awaited”错误信息:
async def get_data(url):
...
return result
async def main():
url = "http://www.example.com"
data = get_data(url)
if __name__ == '__main__':
asyncio.run(main())
示例一:网页爬虫程序
下面是一个简单的网页爬虫程序,采用异步编程模式,实现对多个url的并发访问和数据爬取:
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
urls =['http://www.example1.com', 'http://www.example2.com', 'http://www.example3.com']
tasks = [asyncio.ensure_future(fetch(session, url)) for url in urls]
pages = await asyncio.gather(*tasks)
for page in pages:
print(page)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
在上述程序中,我们使用ClientSession类创建一个异步会话对象,实现并发访问多个url。通过创建多个协程去调用fetch函数,使用asyncio.gather方法将多个协程合并,最终得到多个网页的内容,并打印出来。
示例二:TCP客户端
下面是一个简单的TCP客户端,采用异步编程模式,实现对于多个IP和端口的并发连接和数据传输:
import asyncio
import socket
async def tcp_echo_client(ip, port, message):
reader, writer = await asyncio.open_connection(ip, port)
print(f'send message: {message}')
writer.write(message.encode())
await writer.drain()
data = await reader.read(100)
print(f'receive message: {data.decode()}')
writer.close()
async def main():
messages = [f'message{i}' for i in range(10)]
tasks = [asyncio.ensure_future(tcp_echo_client('localhost', 9000, message)) for message in messages]
await asyncio.wait(tasks)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
在上述程序中,我们使用asyncio.open_connection方法创建一个异步TCP连接,实现对多个IP和端口的并发连接。通过创建多个协程去调用tcp_echo_client函数,使用asyncio.wait方法将多个协程合并,并且等待所有协程执行完毕。最终,程序输出每个连接收到的数据。