如何使用三重奏构造开始和结束同步调用?

时间:2019-06-20 14:03:44

标签: python-trio

我的要求是使用结构化的三重伪代码(实际的三重函数调用,但此处需要填写伪worker-does-work-work),因此我可以理解并尝试在同步和异步之间进行切换的良好流控制实践程序。

我要执行以下操作...

  • 将json-data文件加载到data-dict中
    • 放在一边:数据字典看起来像{'key_a':{(info_dict_a)},'key_b':{info_dict_b}}
  • 每个n位工人都有...
    • 访问该数据字典以查找下一个要处理的记录信息字典
    • 从正在处理的记录中准备一些数据,并将其发布到url
    • 处理后响应以更新正在记录处理的信息字典中的“响应”键
    • 使用密钥的信息字典更新数据字典
    • 用更新后的data-dict覆盖json-data的原始文件

除了:我知道除了笨拙的重复重写json文件外,还有其他方法可以实现我的总体目标-但我不是在要求输入;我真的很想充分理解三重奏,以便能够将其用于 this 流程。

因此,我要同步的进程:

  • 获取下一个要处理的记录信息字典
  • 数据字典的更新
  • 用更新后的data-dict覆盖json-data的原始文件

三重奏的新手,我有工作代码here ...,我相信它是 同步获取下一个要处理的记录(通过使用trio.Semaphore()技术) 。但是我很确定我不会 同步保存文件。

学习Go几年前,我感到自己不满意将同步和异步调用交织在一起的方法-但三重奏还不存在。预先感谢。

2 个答案:

答案 0 :(得分:1)

这是我编写(伪)代码的方式:

    async def process_file(input_file):
        # load the file synchronously
        with open(input_file) as fd:
            data = json.load(fd)

        # iterate over your dict asynchronously
        async with trio.open_nursery() as nursery:
            for key, sub in data.items():
                if sub['updated'] is None:
                    sub['updated'] = 'in_progress'
                    nursery.start_soon(post_update, {key: sub})

        # save your result json synchronously
        save_file(data, input_file)

trio向您保证,一旦退出async with块,您生成的每个任务都已完成,因此您可以安全地保存文件,因为不会再进行更新。

我还删除了grab_next_entry函数,因为在我看来,该函数将在每次调用(赋予O(n!))复杂性时(递增)遍历相同的键,而您可以通过简单地简化它一次遍历您的字典(将复杂度降至O(n))

您也不需要Semaphore,除非您想限制并行post_update的调用次数。但是trio也为此提供了一种内置机制,这要归功于您可以像这样使用它的CapacityLimiter

    limit = trio.CapacityLimiter(10)
    async with trio.open_nursery() as nursery:
        async with limit:
            for x in z:
                nursery.start_soon(func, x)

由于@njsmith的评论而更新

因此,为了限制并发post_update的数量,您将像这样重写它:

    async def post_update(data, limit):
        async with limit:
            ...

然后您可以像这样重写上一个循环:

    limit = trio.CapacityLimiter(10)
    # iterate over your dict asynchronously
    async with trio.open_nursery() as nursery:
        for key, sub in data.items():
            if sub['updated'] is None:
                sub['updated'] = 'in_progress'
                nursery.start_soon(post_update, {key: sub}, limit)

这样,我们为数据字典中的 n 条目生成 n 个任务,但是如果同时运行的任务超过10个,那么额外的任务将必须等待释放限制(在async with limit块的末尾)。

答案 1 :(得分:1)

此代码使用通道多路复用往返工作池的请求。我发现additional requirement(在您的代码注释中)限制了响应后速率,因此read_entries在每个send之后都处于休眠状态。

from random import random    
import time, asks, trio    

snd_input, rcv_input = trio.open_memory_channel(0)
snd_output, rcv_output = trio.open_memory_channel(0)    

async def read_entries():
    async with snd_input:
        for key_entry in range(10):
            print("reading", key_entry)    
            await snd_input.send(key_entry)    
            await trio.sleep(1)    

async def work(n):
    async for key_entry in rcv_input:    
        print(f"w{n} {time.monotonic()} posting", key_entry)    
        r = await asks.post(f"https://httpbin.org/delay/{5 * random()}")
        await snd_output.send((r.status_code, key_entry))

async def save_entries():    
    async for entry in rcv_output:    
        print("saving", entry)    

async def main():    
    async with trio.open_nursery() as nursery:
        nursery.start_soon(read_entries)    
        nursery.start_soon(save_entries)    
        async with snd_output:
            async with trio.open_nursery() as workers:
                for n in range(3):
                    workers.start_soon(work, n)

trio.run(main)