Python asyncio loop.call_soon为何会出现数据覆盖问题?
为什么多次用
loop.call_soon提交异步函数时参数会被覆盖? 这是Python里lambda在循环中捕获变量的经典陷阱,和asyncio本身关系不大,咱们一步步拆解来看:
首先看你的异步代码片段:
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)))
这里的lambda表达式并没有立即捕获当前迭代的k和v的值,而是捕获了变量k和v的引用。当事件循环调度这些lambda执行时,for循环已经跑完了所有迭代,k和v都停留在最后一次循环的结果(也就是'c'和3),所以三个异步任务执行时,都会用这组最后的值,就出现了你看到的重复输出。
再看同步版本的代码:
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)
这里直接把k和v作为参数传给call_soon,call_soon会立即保存这些参数的当前值,等调度执行时用的是当时传入的副本,所以每个任务都拿到了对应迭代的正确参数,输出自然正常。
解决方法
这里有几种常用的方式避免这个陷阱:
方法1:给lambda添加默认参数捕获当前值
修改异步部分的lambda,用默认参数绑定当前迭代的k和v——默认参数会在定义lambda时就求值,确保每个lambda拿到的是当前迭代的副本:
async def calling_func(self): for k, v in self.test_data.items(): print("Sending", k, v) self.loop.call_soon(lambda k=k, v=v: asyncio.ensure_future(self.do_something(k, v)))
方法2:使用functools.partial绑定参数
用functools.partial包装要执行的函数和参数,同样能确保每个任务拿到正确的参数:
from functools import partial async def calling_func(self): for k, v in self.test_data.items(): print("Sending", k, v) task = partial(asyncio.ensure_future, self.do_something(k, v)) self.loop.call_soon(task)
方法3:封装成内部函数
把循环内的调度逻辑放到一个小函数里,每次调用时传入k和v,避免引用问题:
async def calling_func(self): def schedule_task(k, v): asyncio.ensure_future(self.do_something(k, v)) for k, v in self.test_data.items(): print("Sending", k, v) self.loop.call_soon(schedule_task, k, v)
修改后运行代码,输出就会和同步版本一致:
Sending a 1 Sending b 2 Sending c 3 Values a 1 Values b 2 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
内容的提问来源于stack exchange,提问作者svs
相关产品推荐
相关产品推荐

