asyncio:推测等待多个期货

时间:2017-10-17 07:59:37

标签: python python-asyncio

TL; DR

我如何等待未来期货的任何未来,可选择通知其他期货“不再需要它们”?

这就是我需要这个的原因。我想创建一种特殊的未来/任务来执行计时,如果它们累积超过一些超时(或者在与此计时任务交互后被迫停止),可以与其他期货/任务一起使用以取消它们。如果您熟悉Go,则它具有类似的概念Context

为了使这更加具体,想象一下。您有一个典型的HTTP客户端。它需要连续执行几个可能永远阻塞的操作,以便从URL请求页面。例如,这些操作可能是:

  1. 分配一个套接字。
  2. 连接服务器。
  3. 以多个块的形式检索页面。
  4. 关闭连接。
  5. 取消分配套接字。
  6. 假设您允许整个操作花费一分钟。但是你也知道分配一个套接字不应该花费超过一毫秒,连接也可能需要一分钟,同样用于检索块。断开连接和资源释放应该花费几毫秒。

    现在假设您要在每个项目符号点等待完全超时 - 嗯,您的配额超过了两倍。因此,您需要将每次调用的计算delta传递给其后继者。此外,假设您无法释放套接字 - 嗯,没什么大不了的,应用程序可能会从此错误中恢复,因此您还需要区分各种超时。我想这可能是这样写的(在一些想象中的Python版本中):

    async def http_request(context, url):
        socket = await min(allocate_socket(), context.timeout, socket_timeout)
        await min(socket.connect(), context.timeout, connect_timeout)
        async for chunk in min(socket.receive(), context.timeout, chunk_timeout):
            print(chunk)
        await min(socket.close(), context.timeout, close_timeout)
    

1 个答案:

答案 0 :(得分:1)

async_timeout正是您所需要的,您的代码将如下所示:

from async_timeout import timeout


async def http_request(url):
    async with timeout(timeout_for_all):

        async with timeout(socket_timeout):
            socket = await allocate_socket()

        async with timeout(connect_timeout):
            await socket.connect()

        async with timeout(chunk_timeout):
            async for chunk in socket.receive():
                print(chunk)

        async with timeout(close_timeout):
            await socket.close()

让我们检查您提到的问题。

  

Go的样式Context也可以有cancel()方法允许   无论花费多少时间,都要从外部取消流程   等待。

无论超时或其他任何事情,

asyncio都有办法取消任何正在运行的任务。您应该在某个任务上调用cancel()方法(在其中会引发asyncio.CancelledError)并等待任务传播它(可能会抑制异常):

task.cancel()
with suppress(asyncio.CancelledError):
    await task

这是在事情发生之前取消事物的标准方法。你不需要那么复杂的东西。

  

它也可以根据挂钟或内部计时器到期。

我不确定我是否理解这一点,但是async_timeout能够准确地提供您想要的内容 - 一种在某个具体时间限制任务执行的方法。

  

另外,如果没有直接在asyncio中实现,我担心   作为一个单独的线程,那么它将不得不等待预定的/   阻止协程完成而不管超时(它只会   如果执行的协程进入,则能够取消执行   睡眠)。

在某种意义上,创建

asyncio模块本身是为了避免使用多个线程。理想情况下,您的异步程序应该使用单线程内单个事件循环管理的许多协同程序。

这个常见的事件循环管理在它们发生时发生的事情。运行1秒后,此代码将引发TimeoutError

async with timeout(1):
    await asyncio.sleep(20)

<强> UPD:

  

另一个例子是我需要等待多个工人   当我只关心他们中的一个时,完成某项任务   完成它,但我根本不关心超时。

也可以使用标准的asyncio功能来完成:

# Start 3 concurrent tasks (workers):
task_1 = asyncio.ensure_future(coro())
task_2 = asyncio.ensure_future(coro())
task_3 = asyncio.ensure_future(coro())

# Wait first of them done:
tasks = (task_1, task_2, task_3,)
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
print('done', done.pop().result())

# Cancel others since they're still running, 
# but we don't need them to be finished:
for task in pending:
    task.cancel()
    with suppress(asyncio.CancelledError):
        await task