了解 asyncio:异步与同步回调
Understanding asyncio: Asynchronous vs synchronous callbacks
关于 asyncio
有一件我似乎无法理解的非常特别的事情。这就是异步和同步回调之间的区别。让我介绍一些例子。
Asyncio TCP 示例:
class EchoServer(asyncio.Protocol):
def connection_made(self, transport):
print('connection made')
def data_received(self, data):
print('data received: ', data.decode())
def eof_received(self):
pass
def connection_lost(self, exc):
print('connection lost:', exc)
Aiohttp 示例:
async def simple(request):
return Response(text="Simple answer")
async def init(loop):
app = Application(loop=loop)
app.router.add_get('/simple', simple)
return app
loop = asyncio.get_event_loop()
app = loop.run_until_complete(init(loop))
run_app(app, loop=loop)
这两个示例在功能上非常相似,但它们似乎都以不同的方式实现。在第一个示例中,如果您希望在某些操作上得到通知,您可以指定一个同步函数 (EchoServer.connection_made
)。然而,在第二个例子中,如果你想在某些动作上得到通知,你必须定义一个异步回调函数(simple
)。
请问这两种回调有什么区别?我了解常规函数和异步函数之间的区别,但我无法理解回调的区别。例如,如果我想写一个像 aiohttp
这样的异步 API,并且我有一个函数可以做某事并在之后调用回调,我将如何决定是否应该要求一个异步函数作为参数传递还是只是一个常规的同步函数?
在 aiohttp
示例中,您可以从 simple
网络处理程序执行异步调用:访问数据库、发出 http 请求等。
在 Protocol.data_received()
中,您应该只调用常规的同步方法。
UPD
Protocol
回调在设计上应该是同步的。
它们是同步和异步之间非常低级的桥梁。
您可以从他们那里调用异步代码,但这需要非常棘手的编码。
用户级别 asyncio
API for sockets 等是流:https://docs.python.org/3/library/asyncio-stream.html
当您引入自己的回调系统时,您可能需要异步回调,除非您 %100 确定为什么回调永远不想调用异步代码。
常规函数 (def
) 和协程 (async def
) 具有不同的签名。很难更改所需的签名,尤其是当您的代码已作为库发布并且您无法控制回调的所有用户时。
P.S.
任何 public API 方法也是如此。
我在我的库开发过程中学到的最难的一课是:.close()
方法应该是一个协程,即使最初它只调用同步函数,例如socket.close()
.
但稍后您可能会想要添加正常关机,这需要等待当前 activity 完成等等。
如果您的用户将您的 api 称为 obj.close()
,现在他们应该使用 await obj.close()
,但它向后不兼容 更改!
关于 asyncio
有一件我似乎无法理解的非常特别的事情。这就是异步和同步回调之间的区别。让我介绍一些例子。
Asyncio TCP 示例:
class EchoServer(asyncio.Protocol):
def connection_made(self, transport):
print('connection made')
def data_received(self, data):
print('data received: ', data.decode())
def eof_received(self):
pass
def connection_lost(self, exc):
print('connection lost:', exc)
Aiohttp 示例:
async def simple(request):
return Response(text="Simple answer")
async def init(loop):
app = Application(loop=loop)
app.router.add_get('/simple', simple)
return app
loop = asyncio.get_event_loop()
app = loop.run_until_complete(init(loop))
run_app(app, loop=loop)
这两个示例在功能上非常相似,但它们似乎都以不同的方式实现。在第一个示例中,如果您希望在某些操作上得到通知,您可以指定一个同步函数 (EchoServer.connection_made
)。然而,在第二个例子中,如果你想在某些动作上得到通知,你必须定义一个异步回调函数(simple
)。
请问这两种回调有什么区别?我了解常规函数和异步函数之间的区别,但我无法理解回调的区别。例如,如果我想写一个像 aiohttp
这样的异步 API,并且我有一个函数可以做某事并在之后调用回调,我将如何决定是否应该要求一个异步函数作为参数传递还是只是一个常规的同步函数?
在 aiohttp
示例中,您可以从 simple
网络处理程序执行异步调用:访问数据库、发出 http 请求等。
在 Protocol.data_received()
中,您应该只调用常规的同步方法。
UPD
Protocol
回调在设计上应该是同步的。
它们是同步和异步之间非常低级的桥梁。
您可以从他们那里调用异步代码,但这需要非常棘手的编码。
用户级别 asyncio
API for sockets 等是流:https://docs.python.org/3/library/asyncio-stream.html
当您引入自己的回调系统时,您可能需要异步回调,除非您 %100 确定为什么回调永远不想调用异步代码。
常规函数 (def
) 和协程 (async def
) 具有不同的签名。很难更改所需的签名,尤其是当您的代码已作为库发布并且您无法控制回调的所有用户时。
P.S.
任何 public API 方法也是如此。
我在我的库开发过程中学到的最难的一课是:.close()
方法应该是一个协程,即使最初它只调用同步函数,例如socket.close()
.
但稍后您可能会想要添加正常关机,这需要等待当前 activity 完成等等。
如果您的用户将您的 api 称为 obj.close()
,现在他们应该使用 await obj.close()
,但它向后不兼容 更改!