今天晚上,我正在浏览python asyncio模块文档,为我的一个课程项目寻找一些想法,但是我很快发现python的标准aysncio模块可能缺少一些特性。在
如果你浏览一下文档,你会发现有一个基于回调的API和一个基于协程的API。回调API可以用于构建UDP和TCP应用程序,而协程API似乎只能用于构建TCP应用程序,因为它使用流式API。在
这给我带来了一个问题,因为我正在为UDP网络寻找一个基于协程的API,尽管我确实发现asyncio支持低级的基于协程的socket方法,比如^{}和{a2},但是UDP联网的关键API recvfrom和{}并不存在。在
我想写一些代码,比如:async def handle_income_packet(sock):
await data, addr = sock.recvfrom(4096)
# data handling here...
await sock.sendto(addr, response)
我知道可以使用回调API等效地实现这一点,但这里的问题是回调不是协程而是常规函数,因此在它中您无法将控制权交回事件循环并保持函数的执行状态。在
看看上面的代码,如果我们需要在数据处理部分做一些阻塞IO的操作,那么只要我们的IO操作也在协程中完成,那么协程版本就不会有问题:
^{pr2}$
只要我们使用await,事件循环就会从该点开始获取控制流来处理其他事情,直到IO完成。但遗憾的是,这些代码目前还不可用,因为我们在asyncio中没有socket.sendto和{}的协同程序版本。在
我们可以使用传输协议回调API来实现这一点:class EchoServerClientProtocol(asyncio.Protocol):
def connection_made(self, transport):
peername = transport.get_extra_info('peername')
self.transport = transport
def data_received(self, data):
info = requests.get(...)
response = generate_response_from_info(info)
self.transport.write(response)
self.transport.close()
我们不能在那里await一个协程,因为回调不是协程,使用上面这样的阻塞IO调用会在回调中暂停控制流,并阻止循环处理任何其他事件,直到IO完成
另一个推荐的实现思想是在data_received函数中创建一个Future对象,将其添加到事件循环中,并将所需的任何状态变量存储在Protocol类中,然后显式地将控制权返回给循环。虽然这可以工作,但它确实创建了许多复杂的代码,在协同程序版本中,这些代码在任何情况下都不需要。在
另外,here我们有一个使用非阻塞套接字和add_reader处理UDP套接字的示例。但是与协同程序版本的几行代码相比,代码看起来仍然很复杂。在
我想说的是,协同程序是一个非常好的设计,它可以在一个线程中利用并发的能力,同时也有一个非常简单的设计模式,可以节省脑力和不必要的代码行,但是,在我们的asyncio标准库中,让它为UDP网络工作的关键部分确实缺乏。在
你们觉得怎么样?在
另外,如果对支持这种UDP网络API的第三方库有什么建议,我将非常感谢我的课程项目。我发现Bluelet很像这样的东西,但它似乎没有被积极地维护。在
编辑:
似乎这个PR确实实现了这个特性,但是被asyncio开发人员拒绝了。开发人员声称所有函数都可以使用create_datagram_endpoint(),即协议传输API来实现。但是正如我在上面讨论过的,与在许多用例中使用回调API相比,协同路由API具有简单的优点,很不幸我们没有UDP。在