【问题标题】:Why does python asyncio loop.call_soon overwrite data?为什么 python asyncio loop.call_soon 会覆盖数据?
【发布时间】:2018-05-11 18:52:35
【问题描述】:

我在我们的代码中创建了一个难以追踪的错误,但不明白为什么会发生。多次推送相同的异步函数以很快调用时会出现问题。同步函数不会发生这种情况。

以下是该问题的运行示例:

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 的指针被覆盖。

【问题讨论】:

    标签: python python-asyncio event-loop


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

      您的问题实际上与asyncio 无关。 lambda: asyncio.ensure_future(self.do_something(k, v)) 中的 kv 仍然指的是外部作用域中的变量。当您调用函数时,它们的值会发生变化:

      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)

      【讨论】:

        【解决方案3】:

        除了其他人对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))
        

        create_taskpreferable to ensure_future 当你处理协程时。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-11-04
          • 1970-01-01
          • 1970-01-01
          • 2014-04-28
          • 2017-05-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多