【问题标题】:python asyncio asynchronously fetch data by key from a dict when the key becomes available当密钥可用时,python asyncio 通过密钥从字典中异步获取数据
【发布时间】:2020-01-09 07:30:52
【问题描述】:

正如标题所说,我的用例是这样的:

我有一个 aiohttp 服务器,它接受来自客户端的请求,当我收到请求时,我为它生成一个唯一的请求 ID,然后我将 {req_id: req_pyaload} dict 发送给一些工作人员(工作人员不在 python 中,因此正在运行在另一个过程中),当工作人员完成工作时,我会返回响应并将它们放入结果字典中,如下所示:{req_id_1: res_1, req_id_2: res_2}

然后我希望我的 aiohttp 服务器处理程序位于result dict 上方的await,因此当特定响应可用时(通过 req_id)它可以将其发送回来。

我构建了下面的示例代码以尝试模拟该过程,但在实现协程 async def fetch_correct_res(req_id) 时卡住了,该协程应该异步/非阻塞获取req_id 的正确响应。

import random
import asyncio
import shortuuid

n_tests = 1000

idxs = list(range(n_tests))

req_ids = []
for _ in range(n_tests):
    req_ids.append(shortuuid.uuid())

res_dict = {}

async def fetch_correct_res(req_id):
    pass

async def handler(req):
    res = await fetch_correct_res(req)
    assert req == res, "the correct res for the req should exactly be the req itself."
    print("got correct res for req: {}".format(req))

async def randomly_put_res_to_res_dict():
    for _ in range(n_tests):
        random_idx = random.choice(idxs)
        await asyncio.sleep(random_idx / 1000)
        res_dict[req_ids[random_idx]] = req_ids[random_idx]
        print("req: {} is back".format(req_ids[random_idx]))

所以:

  1. 是否可以使此解决方案发挥作用?怎么样?

  2. 如果上述解决方案不可行,对于这个使用 asyncio 的用例,正确的解决方案应该是什么?

非常感谢。


我现在能想到的唯一方法是:预先创建一些带有预先分配 id 的 asyncio.Queue,然后为每个传入的请求分配一个队列给它,所以处理程序只需 await这个队列,当响应返回时,我只将它放入这个预先分配的队列中,在请求完成后,我收集回队列以将其用于下一个传入请求。不是很优雅,但会解决问题。

【问题讨论】:

    标签: python python-3.x async-await python-asyncio aiohttp


    【解决方案1】:

    看看下面的示例实现是否满足您的需求

    基本上你想用你的响应(无法预测顺序)以异步方式响应请求(id)

    所以在处理请求时,在asyncio.Event.wait() 上用{request_id: {'event':<async.Event>, 'result': <result>}}await 填充dict,一旦收到响应,用asyncio.Event.set() 发出事件信号,这将释放等待,然后获取响应从基于请求ID的字典中​​

    我稍微修改了您的代码以使用请求 id 预先填充字典并将 await 放在 asyncio.Event.wait() 上,直到信号来自响应

    import random
    import asyncio
    import shortuuid
    
    n_tests = 10
    
    idxs = list(range(n_tests))
    
    req_ids = []
    for _ in range(n_tests):
        req_ids.append(shortuuid.uuid())
    
    res_dict = {}
    
    async def fetch_correct_res(req_id, event):
      await event.wait()
      res = res_dict[req_id]['result']
      return res
    
    async def handler(req, loop):
          print("incoming request id: {}".format(req))
          event = asyncio.Event()
          data = {req :{}}
          res_dict.update(data)
          res_dict[req]['event']=event
          res_dict[req]['result']='pending'
          res = await fetch_correct_res(req, event)
          assert req == res, "the correct res for the req should exactly be the req itself."
          print("got correct res for req: {}".format(req))
    
    async def randomly_put_res_to_res_dict():
        random.shuffle(req_ids)
        for i in req_ids:
            await asyncio.sleep(random.randrange(2,4))
            print("req: {} is back".format(i))
            if res_dict.get(i) is not None:
              event = res_dict[i]['event']
              res_dict[i]['result'] = i
              event.set()  
    
    loop = asyncio.get_event_loop()
    tasks = asyncio.gather(handler(req_ids[0], loop),
              handler(req_ids[1], loop),
              handler(req_ids[2], loop),
              handler(req_ids[3], loop),
              randomly_put_res_to_res_dict())
    loop.run_until_complete(tasks)
    loop.close()
    

    上述代码的示例响应

    incoming request id: NDhvBPqMiRbteFD5WqiLFE
    incoming request id: fpmk8yC3iQcgHAJBKqe2zh
    incoming request id: M7eX7qeVQfWCCBnP4FbRtK
    incoming request id: v2hAfcCEhRPUDUjCabk45N
    req: VeyvAEX7YGgRZDHqa2UGYc is back
    req: M7eX7qeVQfWCCBnP4FbRtK is back
    got correct res for req: M7eX7qeVQfWCCBnP4FbRtK
    req: pVvYoyAzvK8VYaHfrFA9SB is back
    req: soP8NDxeQKYjgeT7pa3wtG is back
    req: j3rcg5Lp59pQXuvdjCAyZe is back
    req: NDhvBPqMiRbteFD5WqiLFE is back
    got correct res for req: NDhvBPqMiRbteFD5WqiLFE
    req: v2hAfcCEhRPUDUjCabk45N is back
    got correct res for req: v2hAfcCEhRPUDUjCabk45N
    req: porzHqMqV8SAuttteHRwNL is back
    req: trVVqZrUpsW3tfjQajJfb7 is back
    req: fpmk8yC3iQcgHAJBKqe2zh is back
    got correct res for req: fpmk8yC3iQcgHAJBKqe2zh
    

    【讨论】:

      【解决方案2】:

      这可能有效(注意:我删除了 UUID 以便提前知道 req id)

      import random
      import asyncio
      
      n_tests = 1000
      
      idxs = list(range(n_tests))
      
      req_ids = []
      for i in range(n_tests):
          req_ids.append(i)
      
      res_dict = {}
      
      async def fetch_correct_res(req_id):
          while not res_dict.get(req_id):
              await asyncio.sleep(0.1)
      
          return req_ids[req_id]
      
      async def handler(req):
          print("fetching req: ", req)
          res = await fetch_correct_res(req)
          assert req == res, "the correct res for the req should exactly be the req itself."
          print("got correct res for req: {}".format(req))
      
      async def randomly_put_res_to_res_dict(future):
          for i in range(n_tests):
              res_dict[req_ids[i]] = req_ids[i]
              await asyncio.sleep(0.5)
              print("req: {} is back".format(req_ids[i]))
      
          future.set_result("done")
      
      loop = asyncio.get_event_loop()
      future = asyncio.Future()
      asyncio.ensure_future(randomly_put_res_to_res_dict(future))
      loop.run_until_complete(handler(10))
      loop.close()
      

      这是最好的解决方案吗?据我说不,基本上它是一种请求长期运行的工作状态,你应该有(REST)api来完成工作提交和了解工作状态,例如:

      http POST server:port/job
      {some job json paylod}
      Response: 200 OK {"req_id": 1}
      
      http GET server:port/job/1
      Response: 200 OK {"req_id": 1, "status": "in process"}
      
      http GET server:port/job/1
      Response: 200 OK {"req_id": 1, "status": "done", "result":{}}
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-15
        • 1970-01-01
        • 2017-10-01
        • 2019-01-19
        • 2019-05-25
        • 2018-03-07
        相关资源
        最近更新 更多