【问题标题】:Asynchronous python function calls, keep rescheduling functions in asyncio.gather without waiting for longest running task异步 python 函数调用,在 asyncio.gather 中不断重新调度函数,无需等待运行时间最长的任务
【发布时间】:2020-03-12 15:02:18
【问题描述】:

所以目前我有如下所示的异步 python 代码(使用 asyncio):

while datetime.now() < time_limit:
  last_start_run_time = datetime.now()
  result = await asyncio.gather(
    *(
      get_output(output_source)
    )
    for output_source in output_sources
  )
  for output in res:
    output_dict.update(my_output_dict)
  if (datetime.now() - last_start_run_time).seconds < upper_bound_wait:
    await asyncio.sleep(delay)

此代码的问题在于,它总是等待运行时间最长的 get_output 调用为所有输出源再次调用该函数。

我想知道如何重写此代码,使其在完成之前的运行后立即为每个 output_source 调用每个 get_output 调用(如果是在 upper_bound_wait 内),我还希望延迟是每个 get_output 函数调用,而不是在它完成所有函数调用之后。

如何使用 asyncio 来实现?

【问题讨论】:

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


    【解决方案1】:

    建议:将所有逻辑转移到协程中,并在一个简单的循环中创建任务。每个任务将自行决定何时延迟、何时重复以及何时退出。

    async def get_output_repeatable(upper_bound_wait, output_source):
        while datetime.now() < time_limit:
            last_start_run_time = datetime.now()
            output_dict.update(await get_output(output_source))
            if (datetime.now() - last_start_run_time).seconds < upper_bound_wait:
                await asyncio.sleep(delay)
    
    def run_them_all():            
      for output_source in output_sources:
          asyncio.create_task(get_output_repeatedly(upper_bound_wait, output_source))
    

    【讨论】:

    • 我喜欢这种方法的简单性。我认为您需要在每次 while 迭代结束时更新 last_start_run_time。另外,get_output_source 应该是 get_output
    • 啊,是的,这是一个很好的解决方案,因为回答的其他人说它只需要更新 last_start_run_time 就可以了。
    • @user4815162342 对,你是关于 get_output 的事情,现在已修复。至于 last_start_run_time,你必须小心,因为函数 run_them_all() 不是 async def;它会立即返回。当您再次调用它时,您将启动一组新任务,无论旧任务是否完成。我不确定申请要求,所以我没有解决这个问题。
    • 我认为您仍然需要在每次重新开始之前重新设置last_start_run_time,对吧(即在每次while 迭代结束时)?否则它不会是last start_run_time,而是first。这就是要求中的“每个输出源延迟”部分所指的内容。
    • 正如其他用户所说,last_start_run_time 不需要作为参数传递,只需在每次调用 get_output 之前在 while 循环中对其进行初始化
    【解决方案2】:

    您可以在循环中使用asyncio.waitFIRST_COMPLETED。您需要将原始来源存储在某处,例如在传递给asyncio.wait 和从asyncio.wait 返回的未来/任务对象上。这样,循环可以使用相同的源重新调用 get_output 以重新安排它。同样,为了使每个get_output-invocation 的延迟,您需要存储每个先前调用的开始时间,可能也在未来/任务上。例如(未经测试):

    async def delayed_run(aw):
        await asyncio.sleep(delay)
        return await aw
    
    async def run_sources(output_sources, time_limit, upper_bound_wait):
        output_dict = {}
    
        pending = set()
        for output_source in output_sources:
            fut = asyncio.create_task(get_output(output_source))
            fut.my_start_time = time.time()
            fut.my_source = output_source
            pending.add(fut)
    
        while pending and time.time() < time_limit:
            done, pending = await asyncio.wait(
                pending, return_when=asyncio.FIRST_COMPLETED,
                timeout=time_limit - time.time())
    
            for done_fut in done:
                output_dict.update(done_fut.result())
                new_coro = get_output(done_fut.my_source)
                if time.time() - done_fut.my_start_time < upper_bound_wait:
                    new_coro = delayed_run(new_coro)
                new_fut = asyncio.create_task(new_coro)
                new_fut.my_start_time = time.time()
                new_fut.my_source = done_fut.my_source
                pending.add(new_fut)
    
        return output_dict
    

    【讨论】:

    • 我真的很困惑代码在做什么,是否有一些资源可以解释这种设计模式?还有通过不同输出源的循环在哪里,在这里我只看到 1 调用 get_output(output_source) 但在我的 asyncio.gather 调用 getoutput(output_source) 为每个输出源异步调用。
    • 假设 getoutput 读取并解析一个文件,而 output_source 只是一个文件,例如,在这种情况下它会是什么样子,它需要如何更改才能使用您的代码,我也看到了调用 my_factory 之类的东西,我没有看到它定义?我很困惑!
    • @user2968505 是否有一些资源可以解释这种设计模式? - 不,我从您的描述中了解到,代码是为用例定制的。 假设 getoutput 读取并解析一个文件,而 output_source 只是一个文件 - 文件不起作用,因为您肯定知道,Python 文件 IO 不是异步的。 在这里我只看到 1 次调用 get_output(output_source) - 这是您的代码中的错误(对 gather 的调用似乎不正确)和我的误读的结果部分。我将编辑答案以添加get_output 的示例。
    • @user2968505 我现在已经编辑了答案,以省略让您感到困惑的“工厂”想法。代码现在使用适当的源直接调用get_output。它仍然必须将原始源存储在未来对象上(这次存储到my_source,以前是my_factory),因此它知道在当前对象完成后如何创建下一个。
    猜你喜欢
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-14
    • 2021-10-23
    • 2016-09-26
    • 2020-09-08
    相关资源
    最近更新 更多