Python asyncio异步编程常见问题小结

  • Post category:Python

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方法将多个协程合并,并且等待所有协程执行完毕。最终,程序输出每个连接收到的数据。