如何在Trio中收集任务结果?

时间:2018-10-05 18:25:18

标签: python-3.x python-trio

我编写了一个脚本,该脚本使用托儿所和asks模块来循环并根据循环变量调用API。我收到响应,但不知道如何像使用asyncio一样返回数据。

我还有一个问题,即将API限制为每秒5个。

from datetime import datetime
import asks
import time
import trio

asks.init("trio")
s = asks.Session(connections=4)

async def main():
    start_time = time.time()

    api_key = 'API-KEY'
    org_id = 'ORG-ID'
    networkIds = ['id1','id2','idn']

    url = 'https://api.meraki.com/api/v0/networks/{0}/airMarshal?timespan=3600'
    headers = {'X-Cisco-Meraki-API-Key': api_key, 'Content-Type': 'application/json'}

    async with trio.open_nursery() as nursery:
        for i in networkIds:
            nursery.start_soon(fetch, url.format(i), headers)

    print("Total time:", time.time() - start_time)



async def fetch(url, headers):
    print("Start: ", url)
    response = await s.get(url, headers=headers)
    print("Finished: ", url, len(response.content), response.status_code)




if __name__ == "__main__":
    trio.run(main)

当我运行Nursery.start_soon(fetch ...)时,我正在fetch中打印数据,但是如何返回数据?我没有看到类似于asyncio.gather(* tasks)函数的任何东西。

此外,我可以将会话数限制为1-4,这有助于降低到每秒5个API以下的限制,但是我想知道是否存在一种内置方法来确保不超过5个API被调用任何给定的秒数?

4 个答案:

答案 0 :(得分:3)

返回数据:将networkID和字典传递给fetch任务:

async def main():
    …
    results = {}
    async with trio.open_nursery() as nursery:
        for i in networkIds:
            nursery.start_soon(fetch, url.format(i), headers, results, i)
    ## results are available here

async def fetch(url, headers, results, i):
    print("Start: ", url)
    response = await s.get(url, headers=headers)
    print("Finished: ", url, len(response.content), response.status_code)
    results[i] = response

或者,创建一个trio.Queue并将结果put移至其中;然后您的主要任务可以从队列中读取结果。

API限制:创建一个trio.Queue(10)并按照以下步骤启动任务:

async def limiter(queue):
    while True:
        await trio.sleep(0.2)
        await queue.put(None)

将该队列作为另一个参数传递给fetch,并在每次API调用之前调用await limit_queue.get()

答案 1 :(得分:3)

  

当我运行Nursery.start_soon(fetch ...)时,我正在fetch中打印数据,但是如何返回数据?我没有看到类似于asyncio.gather(* tasks)函数的任何东西。

您要问两个不同的问题,所以我只回答一个。 Matthias已经回答了您的其他问题。

致电start_soon()时,您是在要求Trio在后台运行任务,然后继续进行。这就是Trio能够同时运行fetch()多次的原因。但是由于Trio一直在发展,所以没有办法像Python函数通常那样“返回”结果。它甚至会回到哪里?

您可以使用队列让fetch()任务将结果发送到另一个任务以进行其他处理。

要创建队列:

response_queue = trio.Queue()

开始获取任务时,将队列作为参数传递,并在完成后将哨兵发送到队列:

async with trio.open_nursery() as nursery:
    for i in networkIds:
        nursery.start_soon(fetch, url.format(i), headers)
await response_queue.put(None)

下载URL后,将响应放入队列:

async def fetch(url, headers, response_queue):
    print("Start: ", url)
    response = await s.get(url, headers=headers)
    # Add responses to queue
    await response_queue.put(response)
    print("Finished: ", url, len(response.content), response.status_code)

通过上述更改,您的提取任务会将响应放入队列。现在,您需要从队列中读取响应,以便可以对其进行处理。您可以添加一个新功能来做到这一点:

async def process(response_queue):
    async for response in response_queue:
        if response is None:
            break
        # Do whatever processing you want here.

在启动任何提取任务之前,应先将此过程功能作为后台任务启动,以便它在收到响应后立即进行处理。

在Trio文档的Synchronizing and Communicating Between Tasks部分中了解更多信息。

答案 2 :(得分:1)

从技术上讲,trio.Queue在trio 0.9中已被弃用。它已被trio.open_memory_channel取代。

简短示例:

sender, receiver = trio.open_memory_channel(len(networkIds)
async with trio.open_nursery() as nursery:
    for i in networkIds:
        nursery.start_soon(fetch, sender, url.format(i), headers)

async for value in receiver:
    # Do your job here
    pass

fetch函数中,您应该在某个地方调用async sender.send(value)

答案 3 :(得分:0)

基于this answers,您可以定义以下功能:

behavior: merge

然后,您可以通过简单地修补trio(添加collect函数),以与asyncio完全相同的方式使用trio:

async def gather(*tasks):

    async def collect(index, task, results):
        task_func, *task_args = task
        results[index] = await task_func(*task_args)

    results = {}
    async with trio.open_nursery() as nursery:
        for index, task in enumerate(tasks):
            nursery.start_soon(collect, index, task, results)
    return [results[i] for i in range(len(tasks))]

这是一个实际示例:

import trio
trio.gather = gather