sure_future中的计划任务与最后一个任务重叠

时间:2018-10-03 15:20:57

标签: asynchronous python-asyncio aiohttp

我需要一个新的处理程序,该处理程序使用另一个现有的处理程序来一次处理多个请求:

async def process_multiple_queries(request):
    init_request = request.clone()
    body = await request.json()

    for query in body["queries"]:

        @asyncio.coroutine
        def fake_json(self):
            return query

        # Replace request body on new fake json
        R = init_request.clone()
        R.json = fake_json.__get__(R, Request)

        # Process request in the background task
        asyncio.ensure_future(process_query(R))

async def process_query(request):
    body = await request.json()
    # ... Process body ...
    return web.json_response({"result": "OK"})

但是,所有计划任务的结果都等于上一个任务的结果,即process_query中传递的所有请求的主体仅等于上一个请求的主体。

但是,如果我在asyncio.sleep(5)之后添加asyncio.ensure_future(process_query(R)),则所有任务都会得到正确处理并产生不同的结果。

如何解决?

1 个答案:

答案 0 :(得分:0)

这是well-known Python陷阱,其中的闭包按名称而不是按值捕获变量。结果,fake_json协程的所有实例都引用相同的query 变量,因此最终都引用了它的 last 已知值。

要解决此问题,您需要更改定义:

        @asyncio.coroutine
        def fake_json(self):
            return query

表示捕获query的值的公式,例如:

        @asyncio.coroutine
        def fake_json(self, query=query):
            return query

await asyncio.sleep(5)之所以有用,是因为它允许process_queryquery的值从脚下改变之前运行到完成。与await process_query(R)会得到相同的效果。

与问题无关的两点:

  • @asyncio.coroutine装饰器是deprecated,并且没有理由在新代码中使用它(除非您特别希望与3.5之前版本的Python兼容)。相反,只需使用fake_json定义async def

  • 您无需调用__get__即可将self绑定到fake_json,甚至没有使用它。您可以定义一个不fake_json的{​​{1}},而只需分配self