【问题标题】:Python Asyncio task is running without gather()Python Asyncio 任务在没有收集()的情况下运行
【发布时间】:2018-11-29 22:32:48
【问题描述】:

我试图重现并更好地理解 Cristian Garcia 的 this 博客文章中的 TaskPool 示例,结果遇到了一个非常有趣的结果。

这是我使用的两个脚本。我用随机睡眠呼叫替换了一个实际的网络请求

#task_pool.py
import asyncio

class TaskPool(object):

    def __init__(self, workers):
        self._semaphore = asyncio.Semaphore(workers)
        self._tasks = set()

    async def put(self, coro):
        await self._semaphore.acquire()
        task = asyncio.create_task(coro)
        self._tasks.add(task)
        task.add_done_callback(self._on_task_done)

    def _on_task_done(self, task):
        self._tasks.remove(task)
        self._semaphore.release()

    async def join(self):
        await asyncio.gather(*self._tasks)

    async def __aenter__(self):
        return self

    def __aexit__(self, exc_type, exc, tb):
        print("aexit triggered")
        return self.join()

还有

# main.py
import asyncio
import sys
from task_pool import TaskPool
import random
limit = 3

async def fetch(i):
    timereq = random.randrange(5)
    print("request: {} start, delay: {}".format(i, timereq))
    await asyncio.sleep(timereq)
    print("request: {} end".format(i))
    return (timereq,i)

async def _main(total_requests):
    async with TaskPool(limit) as tasks:
        for i in range(total_requests):
            await tasks.put(fetch(i))

loop = asyncio.get_event_loop()
loop.run_until_complete(_main(int(sys.argv[1])))

python 3.7.1 上的命令main.py 10 产生以下结果。

request: 0 start, delay: 3
request: 1 start, delay: 3
request: 2 start, delay: 3
request: 0 end
request: 1 end
request: 2 end
request: 3 start, delay: 4
request: 4 start, delay: 1
request: 5 start, delay: 0
request: 5 end
request: 6 start, delay: 1
request: 4 end
request: 6 end
request: 7 start, delay: 1
request: 8 start, delay: 4
request: 7 end
aexit triggered
request: 9 start, delay: 1
request: 9 end
request: 3 end
request: 8 end

根据这个结果,我有几个问题。

  1. 在上下文管理器退出并触发__aexit__ 之前,我不会期望任务运行,因为这是asyncio.gather 的唯一触发器。然而,打印语句强烈表明fetch 作业甚至在aexit 之前发生。到底发生了什么?任务是否正在运行?如果是这样,是什么开始了他们?
  2. 与 (1) 相关。为什么上下文管理器在所有作业返回之前就退出了?
  3. fetch 作业应该返回一个元组。我怎样才能访问这个值?对于基于 Web 的应用程序,我想开发人员可能希望对网站返回的数据进行操作。

非常感谢任何帮助!

【问题讨论】:

    标签: python python-3.x asynchronous python-asyncio aiohttp


    【解决方案1】:
    1. 一旦调用create_task,任务就会启动。

      直接来自文档,第一行:

      将 coro 协程包装到 Task 中并安排其执行。

    2. 不应该,但是。查看您问题中的代码:

      def __aexit__(self, exc_type, exc, tb):
          print("aexit triggered")
          return self.join()
      

      存在三个问题:

      • 这是一个常规的同步函数。将其更改为async def 并添加强制await 以调用self.join()。在这里你不调用join 你只是创建任务但从不运行它。你的蟒蛇肯定会抱怨你从不等待任务。 绝不能忽略这些警告,因为它们意味着您的程序中出现了严重问题。

        [edit:] 正如user4815162342在下面指出的那样,您编写的构造实际上会起作用,尽管可能不是出于预期的原因-它起作用是因为通过调用self.join()返回的协程函数无需等待它将被退回和使用,就好像它是 aexit 自己的一样。你不想要这个,让它异步并等待。

      • 修复此问题后,__aexit__ 将打印“aexit 已触发”,然后 调用 join,等待任务完成。因此,尚未完成的任务消息将出现在“触发退出”消息之后。

      • __aexit__ 的返回值被忽略,除非由于引发异常而退出。在这种情况下,return True 将吞下异常。删除return

      所以那部分,固定:

      async def __aexit__(self, exc_type, exc, tb):
          print("aexit triggered")
          await self.join()
          print("aexit completed")
      
    3. 您的TaskPool 必须使任务结果可用。它是你的设计,python 不会在引擎盖下做任何魔法。根据您的情况,joingather 的结果存储为任务池的属性是一种简单的方法。

    【讨论】:

    • 请注意,在 OP 的代码中,return self.join() 实际上是必需的结合 __aexit__ 是一个常规函数。这可以正常工作(尽管可能是无意的),就像非生成器函数实例化并返回生成器是正确的一样 - 该函数与实际生成器之间没有功能差异。这就是为什么 OP 可能没有收到警告(关于那部分代码)。你的其余解释仍然是正确的。
    • @user4815162342> 非常棒。它确实最终将join 作为同步退出处理程序运行。这再次证明了 asyncio 对开发人员的友好程度明显低于 python 的其他部分。
    • 我理解为什么感觉不友好,但是能够从同步函数返回协程有时是一个有用的功能。也许需要根据条件在不同的协程/期货之间进行选择,或者可能需要在返回协程之前在同步域中做一些准备。请参阅this answer 以获取由返回实例化async def 的普通函数定义的协程必要 的示例。并且代码是在协程级别编写的,而无需使用生成器。
    • @user4815162342 我理解为什么它很有用,因为 asyncio 是如何实现的,我有时会操纵它们(尽管我倾向于将它封装在一个类或函数中)。我仍然认为这不是 python 设计最好的部分。 :)
    • @CuriousDan> 它存储在某处:某处位于 Future 本身,因此您必须保留对它的引用。对于 2) 要点是调用 async def 函数创建并返回一个协程对象。除非awaited 或明确安排,否则该对象不会运行。在您发布的版本中,aexit 确实返回了一个协程对象(通过调用 self.join() 创建),这就是它没有中断的原因。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-24
    • 2019-02-23
    • 1970-01-01
    • 1970-01-01
    • 2022-07-01
    • 2015-03-02
    • 2017-03-02
    相关资源
    最近更新 更多