为什么python asyncio loop.call\u很快就会覆盖数据?
我在代码中创建了一个难以追踪的bug,但不明白它为什么会发生。当多次推动同一异步函数以尽快调用时,就会出现问题。同步函数不会发生这种情况 以下是该问题的运行示例:为什么python asyncio loop.call\u很快就会覆盖数据?,python,python-asyncio,event-loop,Python,Python Asyncio,Event Loop,我在代码中创建了一个难以追踪的bug,但不明白它为什么会发生。当多次推动同一异步函数以尽快调用时,就会出现问题。同步函数不会发生这种情况 以下是该问题的运行示例: import asyncio import sys class TestObj(object): def __init__(self): self.test_data = {'a': 1, 'b': 2, 'c': 3} self.loop = asyncio.get_event_loop
import asyncio
import sys
class TestObj(object):
def __init__(self):
self.test_data = {'a': 1, 'b': 2, 'c': 3}
self.loop = asyncio.get_event_loop()
self.loop.call_later(1, lambda: asyncio.ensure_future(self.calling_func()))
self.loop.call_later(2, self.calling_func_sync)
self.loop.call_later(4, sys.exit)
self.loop.run_forever()
async def do_something(self, k, v):
print("Values", k, v)
async def calling_func(self):
for k, v in self.test_data.items():
print("Sending", k, v)
self.loop.call_soon(lambda: asyncio.ensure_future(self.do_something(k, v)))
def do_something_sync(self, k, v):
print("Values_sync", k, v)
def calling_func_sync(self):
for k, v in self.test_data.items():
print("Sending_sync", k, v)
self.loop.call_soon(self.do_something_sync, k, v)
if __name__ == "__main__":
a = TestObj()
输出为:
Sending a 1
Sending b 2
Sending c 3
Values c 3
Values c 3
Values c 3
Sending_sync a 1
Sending_sync b 2
Sending_sync c 3
Values_sync a 1
Values_sync b 2
Values_sync c 3
为什么会发生这种情况,为什么?只有异步函数正在被踩踏。我本以为每次调用call_都会很快将一个新指针推到堆栈上,但似乎有一个指向self.do_的指针正在被覆盖。这与异步代码无关,但与循环中创建的lambda有关。当您编写
lambda:asyncio.sure_future(self.do_something(k,v))
时,您正在创建一个闭包,从封闭的名称空间访问变量k
和v
(以及self
,但这不是问题)。调用lambda函数时,它将使用调用时外部作用域中由这些名称绑定的值,而不是定义lambda时的值。由于k
和v
在循环的每次迭代中都会更改值,因此所有lambda函数都会看到相同的值(最后的值)
避免此问题的常见方法是将变量的当前值设为lambda函数参数的默认值:
self.loop.call_soon(lambda k=k, v=v: asyncio.ensure_future(self.do_something(k, v)))
这与异步代码无关,但与在循环中创建的
lambda
有关。当您编写lambda:asyncio.sure_future(self.do_something(k,v))
时,您正在创建一个闭包,从封闭的名称空间访问变量k
和v
(以及self
,但这不是问题)。调用lambda函数时,它将使用调用时外部作用域中由这些名称绑定的值,而不是定义lambda时的值。由于k
和v
在循环的每次迭代中都会更改值,因此所有lambda函数都会看到相同的值(最后的值)
避免此问题的常见方法是将变量的当前值设为lambda函数参数的默认值:
self.loop.call_soon(lambda k=k, v=v: asyncio.ensure_future(self.do_something(k, v)))
您的问题实际上与
asyncio
无关。lambda:asyncio中的k
和v
。确保未来(self.do\u something(k,v))
仍然引用外部范围中的变量。在调用函数时,它们的值会发生变化:
i = 1
f = lambda: print(i)
f() # 1
i = 2
f() # 2
常见的解决方案是定义函数,并(ab)使用默认参数创建函数的局部变量,该变量在创建函数时保存i
的值,而不是调用:
i = 1
f = lambda i=i: print(i)
f() # 1
i = 2
f() # 1
如果命名让您感到困惑,您可以使用
f=lambda x=i:print(x)
。您的问题实际上与异步IO
无关。lambda:asyncio中的k
和v
。确保未来(self.do\u something(k,v))
仍然引用外部范围中的变量。在调用函数时,它们的值会发生变化:
i = 1
f = lambda: print(i)
f() # 1
i = 2
f() # 2
常见的解决方案是定义函数,并(ab)使用默认参数创建函数的局部变量,该变量在创建函数时保存i
的值,而不是调用:
i = 1
f = lambda i=i: print(i)
f() # 1
i = 2
f() # 1
如果命名让您感到困惑,您可以使用
f=lambda x=i:print(x)
。除了其他人对lambda
中错误的正确解释之外,还请注意,您甚至不需要lambda
。由于dou\u something
是一个协同程序,因此在事件循环的下一次迭代之前,仅调用它不会执行其任何代码,因此您很快就会自动获得调用的效果。(这类似于调用生成器函数直到开始使用返回的迭代器才开始执行它。)
换句话说,您可以替换
self.loop.call_soon(lambda: asyncio.ensure_future(self.do_something(k, v)))
用更简单的
self.loop.create_task(self.do_something(k, v))
create\u task
是在处理协同程序时确保未来的。除了其他人对lambda
中错误的正确解释外,还要注意,您甚至不需要lambda
。由于dou\u something
是一个协同程序,因此在事件循环的下一次迭代之前,仅调用它不会执行其任何代码,因此您很快就会自动获得调用的效果。(这类似于调用生成器函数直到开始使用返回的迭代器才开始执行它。)
换句话说,您可以替换
self.loop.call_soon(lambda: asyncio.ensure_future(self.do_something(k, v)))
用更简单的
self.loop.create_task(self.do_something(k, v))
create_task
是确保未来
处理协同程序时的任务