为什么python asyncio loop.call_soon会覆盖数据?

时间:2018-05-11 18:52:35

标签: python python-asyncio 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_soon都会将一个新指针推送到堆栈上,但似乎有一个指向self.do_something的指针被覆盖。

3 个答案:

答案 0 :(得分:3)

这与异步代码无关,但与您在循环中创建的lambda无关。当您编写lambda: asyncio.ensure_future(self.do_something(k, v))时,您正在创建一个闭包,该闭包可以从封闭的命名空间(以及k)访问变量vself,但是'不是问题)。调用lambda函数时,它将使用调用时的外部作用域中这些名称绑定的值, not 定义lambda时的值。由于kv会在循环的每次迭代中更改值,因此会导致所有lambda函数看到相同的值(最后一个)。

避免此问题的常用方法是使变量的当前值为lambda函数的参数的默认值:

self.loop.call_soon(lambda k=k, v=v: asyncio.ensure_future(self.do_something(k, v)))

答案 1 :(得分:1)

您的问题实际上与asyncio无关。 k中的vlambda: asyncio.ensure_future(self.do_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)

答案 2 :(得分:0)

除了其他人对i < c.length;中的错误的正确解释外,还请注意您甚至不需要lambda。由于lambda是一个协程,所以只要调用它就不会执行任何代码,直到事件循环的下一次迭代,所以你自动产生do_something的效果。 (这类似于在开始耗尽返回的迭代器之前调用生成器函数不会开始执行它的方法。)

换句话说,您可以替换

call_soon

更简单

self.loop.call_soon(lambda: asyncio.ensure_future(self.do_something(k, v)))
当你处理协程时,

self.loop.create_task(self.do_something(k, v)) preferable to create_task