Python Asyncio任务正在运行,而没有collect()

时间:2018-11-29 22:32:48

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

我试图重现并更好地理解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的应用程序,我想开发人员可能希望对网站返回的数据进行操作。

非常感谢您的帮助!

1 个答案:

答案 0 :(得分:2)

  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,而只是创建任务而从不运行它。您的python肯定会抱怨您从未等待任务。 这些警告绝不能忽略,因为它们表示您的程序中出现了非常严重的错误。

      [edit:] 如下面的user4815162342所指出的那样,您编写的构造实际上可以工作,尽管可能不是出于预期的原因-之所以起作用是因为通过调用self.join()返回的协程函数无需等待即可将其返回并使用,就好像它是出口本身一样。您不需要它,使其异步并等待。

    • 解决此问题后,__aexit__将打印“退出触发”,然后 then 调用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的结果存储为任务池的属性。