Python 使用asyncio处理超时

Python 使用asyncio处理超时,python,python-asyncio,Python,Python Asyncio,免责声明:这是我第一次尝试asyncio模块 我正在以以下方式使用asyncio.wait,以尝试支持等待一组异步任务的所有结果的超时功能。这是一个更大的库的一部分,所以我省略了一些不相关的代码 请注意,该库已经支持提交任务和使用ThreadPoolExecutors和ProcessPoolExecutors的超时,因此我对使用这些任务的建议并不感兴趣,也对我为什么使用asyncio进行此操作的问题不感兴趣。关于代码 import asyncio from contextlib import s

免责声明:这是我第一次尝试
asyncio
模块

我正在以以下方式使用
asyncio.wait
,以尝试支持等待一组异步任务的所有结果的超时功能。这是一个更大的库的一部分,所以我省略了一些不相关的代码

请注意,该库已经支持提交任务和使用ThreadPoolExecutors和ProcessPoolExecutors的超时,因此我对使用这些任务的建议并不感兴趣,也对我为什么使用
asyncio
进行此操作的问题不感兴趣。关于代码

import asyncio
from contextlib import suppress

... 

class AsyncIOSubmit(Node):
    def get_results(self, futures, timeout=None):
        loop = asyncio.get_event_loop()
        finished, unfinished = loop.run_until_complete(
            asyncio.wait(futures, timeout=timeout)
        )
        if timeout and unfinished:
            # Code options in question would go here...see below.
            raise asyncio.TimeoutError
起初我并不担心在超时时取消挂起的任务,但后来我得到警告,任务已被销毁,但它正在挂起循环时编码>。关闭。经过研究,我找到了多种方法来取消任务并等待它们真正被取消:

选项1:

[task.cancel() for task in unfinished]
for task in unfinished:
    with suppress(asyncio.CancelledError):
        loop.run_until_complete(task)
[task.cancel() for task in unfinished]
loop.run_until_complete(asyncio.wait(unfinished))
# Not really an option for me, since I'm not in an `async` method
# and don't want to make get_results an async method.
[task.cancel() for task in unfinished]
for task in unfinished:
    await task
选项2:

[task.cancel() for task in unfinished]
for task in unfinished:
    with suppress(asyncio.CancelledError):
        loop.run_until_complete(task)
[task.cancel() for task in unfinished]
loop.run_until_complete(asyncio.wait(unfinished))
# Not really an option for me, since I'm not in an `async` method
# and don't want to make get_results an async method.
[task.cancel() for task in unfinished]
for task in unfinished:
    await task
选项3:

[task.cancel() for task in unfinished]
for task in unfinished:
    with suppress(asyncio.CancelledError):
        loop.run_until_complete(task)
[task.cancel() for task in unfinished]
loop.run_until_complete(asyncio.wait(unfinished))
# Not really an option for me, since I'm not in an `async` method
# and don't want to make get_results an async method.
[task.cancel() for task in unfinished]
for task in unfinished:
    await task
选项4:

[task.cancel() for task in unfinished]
for task in unfinished:
    with suppress(asyncio.CancelledError):
        loop.run_until_complete(task)
[task.cancel() for task in unfinished]
loop.run_until_complete(asyncio.wait(unfinished))
# Not really an option for me, since I'm not in an `async` method
# and don't want to make get_results an async method.
[task.cancel() for task in unfinished]
for task in unfinished:
    await task
某种while循环式的回答。似乎我的其他选择更好,但包括完整性


到目前为止,选项1和2似乎都很有效。这两种选择都可能是“正确的”,但随着
asyncio
多年来的发展,网络上的例子和建议要么已经过时,要么变化很大。所以我的问题是

问题1

选项1和选项2之间是否存在实际差异?我知道
run_直到_complete
将一直运行到将来完成,因此,由于选项1是按特定顺序循环的,因此我认为,如果早期任务实际完成所需时间较长,则其行为可能会有所不同。我试着查看asyncio源代码,以了解
asyncio.wait
是否在幕后有效地处理了它的任务/未来,但这并不明显

问题2

我假设如果一个任务在一个长时间运行的阻塞操作的中间,它可能不会立即取消?也许这仅仅取决于所使用的底层操作或库是否会立即引发CanceledError?对于为asyncio设计的库,也许永远不会发生这种情况

因为我试图在这里实现一个超时特性,所以我对这个有点敏感。如果可能的话,这些事情可能要花很长时间才能取消,我会考虑调用<代码>取消<代码>,而不是等待它实际发生,或者设置一个非常短的超时时间等待取消完成。< /P> 问题3

是否可能
loop.run_直到_complete
(或者实际上,对
async.wait
的底层调用)返回
unfinished
中的值,原因不是超时?如果是这样的话,我显然不得不调整一下我的逻辑,但从表面上看,这似乎是不可能的

选项1和选项2之间是否存在实际差异

不。选项2看起来更好,效率可能略高,但它们的净效果是一样的

我知道
run_直到_complete
将一直运行到将来完成,因此,由于选项1是按特定顺序循环的,因此我认为,如果早期任务实际完成所需时间较长,则其行为可能会有所不同

一开始似乎是这样,但事实并非如此,因为
loop.run_直到_complete
运行提交给循环的所有任务,而不仅仅是作为参数传递的任务。它只在所提供的等待完成后停止,这就是“运行直到完成”所指的。对已安排的任务执行循环调用
run\u直到\u完成
,类似于以下异步代码:

ts=[asyncio.create_task(asyncio.sleep(i)),用于范围(1,11)内的i)
#需要10秒,而不是55秒
对于ts中的t:
等待
这又在语义上等同于以下线程代码:

ts=[]
对于范围(1,11)内的i:
t=threading.Thread(target=time.sleep,args=(i,))
t、 开始()
ts.append(t)
#需要10秒,而不是55秒
对于ts中的t:
t、 加入
换句话说,
等待t
运行直到完成(t)
阻塞直到
t
完成,但允许所有其他操作,例如之前使用
asyncio.create_task()
计划的任务也在这段时间内运行。因此,总运行时间将等于最长任务的运行时间,而不是它们的总和。例如,如果第一项任务恰好需要很长时间,那么所有其他任务都将在这段时间内完成,而他们的等待将根本无法入睡

所有这些仅适用于先前计划的等待任务。如果您试图将其应用于协同程序,那么它将不起作用:

#按预期运行55秒
对于范围(1,11)内的i:
等待异步睡眠(i)
#还有55s-我们没有调用create_task(),因此它与上面的相同
ts=[asyncio.sleep(i)for i在范围(1,11)内]
对于ts中的t:
等待
#还有55s
对于范围(1,11)内的i:
t=threading.Thread(target=time.sleep,args=(i,))
t、 开始()
t、 加入
对于asyncio初学者来说,这通常是一个关键点,他们编写与上一个asyncio示例等效的代码,并期望它并行运行

我试着查看asyncio源代码,以了解
asyncio.wait
是否在幕后有效地处理了它的任务/未来,但这并不明显

asyncio.wait
只是一个方便的API,它可以做两件事:

  • 将输入参数转换为实现
    Future
    的参数。对于协同程序,这意味着它将它们提交到事件循环,就像使用
    create\u task
    一样,这允许它们独立运行。如果您一开始就给it分配任务,那么将跳过此步骤
  • 使用
    add_done_callback
    在期货完成时收到通知,此时它将恢复其调用者
  • <