Asynchronous 在不等待其他协同路由完成的情况下重新启动异步协同路由

Asynchronous 在不等待其他协同路由完成的情况下重新启动异步协同路由,asynchronous,async-await,python-asyncio,python-3.7,Asynchronous,Async Await,Python Asyncio,Python 3.7,我正在用Python进行异步编程,遇到以下问题: 模拟多人从同一个食物碗中吃一定数量的食物。每个人一次可以吃x份食物,然后咀嚼食物y秒(模拟阻塞呼叫)。只要碗里还有食物,一个人就可以独立于他人进食和咀嚼食物 为每个食客和食物碗定义类。最终目标是在FoodBowl类中提供一个函数,该函数接受一个人员列表,并让他们开始从碗中进食,直到碗空为止。每当有人从碗里拿食物时,都应该在标准输出上打印一条信息 例如,如果我有一个盛有25份食物的碗,还有三个人,a、B和C: A每次吃2份食物,咀嚼3秒钟 B一次

我正在用Python进行异步编程,遇到以下问题:

模拟多人从同一个食物碗中吃一定数量的食物。每个人一次可以吃x份食物,然后咀嚼食物y秒(模拟阻塞呼叫)。只要碗里还有食物,一个人就可以独立于他人进食和咀嚼食物

为每个食客和食物碗定义类。最终目标是在FoodBowl类中提供一个函数,该函数接受一个人员列表,并让他们开始从碗中进食,直到碗空为止。每当有人从碗里拿食物时,都应该在标准输出上打印一条信息

例如,如果我有一个盛有25份食物的碗,还有三个人,a、B和C:

  • A每次吃2份食物,咀嚼3秒钟
  • B一次吃3份食物,咀嚼4秒钟
  • C每次吃5份食物,咀嚼2秒钟
因此,预期输出(打印到标准输出)应为:

(t=0) Person A takes 2 servings of food, leaving 23 servings in the bowl.
(t=0) Person B takes 3 servings of food, leaving 20 servings in the bowl.
(t=0) Person C takes 5 servings of food, leaving 15 servings in the bowl.
(t=2) Person C takes 5 servings of food, leaving 10 servings in the bowl.
(t=3) Person A takes 2 servings of food, leaving 8 servings in the bowl.
(t=4) Person B takes 3 servings of food, leaving 5 servings in the bowl.
(t=4) Person C takes 5 servings of food, leaving 0 servings in the bowl.
(t=4) The bowl is empty!
(在两个人准备再吃一份的时候,如
t=4
,顺序无关紧要) 代码是我的尝试:

导入异步IO
导入时间
类Person():
定义初始(自我、姓名、服务大小、进食时间):
self.name=名称
自助式大小=自助式大小
self.time\u to\u eat=吃饭时间
异步def eat_from(self,foodbowl):
食用量=自助式大小(如果foodbowl.qty>=自助式大小,否则foodbowl.qty>=自助式大小)
foodbowl.qty-=食用量
t=round(time.time()-foodbowl.start\u时间)
打印(“(t={})人{}拿起{}份食物,把{}份食物留在碗里。”.format(t,self.name,吃的份数,foodbowl.qty))
等待异步睡眠(自我进食时间)
退换货
类FoodBowl():
定义初始(自身,数量):
自身数量=数量
异步定义分配_eaters(self,eaters):
self.start\u time=time.time()
当self.qty>0时:
等待asyncio.gather(*[eater.eat\u from(self)for eater in eater])
t=整周(time.time()-self.start\u时间)
打印(“碗是空的!”)
碗=FoodBowl(25)
person_1=person(“A”,2,3)
person_2=person(“B”,3,4)
person_3=person(“C”,5,2)
asyncio.run(bowl.assign_-eaters([person_1,person_2,person_3]))
但是,我的尝试会导致以下行为:

(t=0) Person A picks up 2 servings of food, leaving 23 servings in the bowl.
(t=0) Person B picks up 3 servings of food, leaving 20 servings in the bowl.
(t=0) Person C picks up 5 servings of food, leaving 15 servings in the bowl.
(t=4) Person A picks up 2 servings of food, leaving 13 servings in the bowl.
(t=4) Person B picks up 3 servings of food, leaving 10 servings in the bowl.
(t=4) Person C picks up 5 servings of food, leaving 5 servings in the bowl.
(t=8) Person A picks up 2 servings of food, leaving 3 servings in the bowl.
(t=8) Person B picks up 3 servings of food, leaving 0 servings in the bowl.
(t=8) Person C picks up 0 servings of food, leaving 0 servings in the bowl.
The bowl is empty!
可以看出,每个人都会等待每个人吃完自己的菜,然后再伸手去拿碗。看看我的代码,我知道这是因为我在eat函数上等待了
asyncio.gather()
,因此它将等待所有三个人吃完,然后再开始吃饭

我知道这是错误的,但我不知道我可以在
asyncio
库中使用什么来解决这个问题。我想到的是,只要碗里还有食物,coroutine就会自动重启。我如何做到这一点,或者有更好的方法解决这个问题

我知道[等待所有三个人吃完后再开始吃饭]是错误的,但我不知道我可以在asyncio库中使用什么来解决这个问题

您可以使用
wait(return\u when=asyncio.FIRST\u COMPLETED)
来等待任何食客完成,而不是像当前代码那样等待所有食客完成。每当一个食客完成进食时,为同一个食客生成一个新的协同程序,有效地“重新启动”它。这需要
wait
返回给eater的任务的引用;这样的引用可以很容易地附加到
任务
对象。代码可能如下所示:

async def assign_eaters(self, eaters):
    self.start_time = time.time()
    # create the initial tasks...
    pending = [asyncio.create_task(eater.eat_from(self))
               for eater in eaters]
    # ...and store references to their respective eaters
    for t, eater in zip(pending, eaters):
        t.eater = eater

    while True:
        done, pending = await asyncio.wait(
            pending, return_when=asyncio.FIRST_COMPLETED)
        if self.qty == 0:
            break
        for t in done:
            # re-create the coroutines that have finished
            new = asyncio.create_task(t.eater.eat_from(self))
            new.eater = t.eater
            pending.add(new)
    t = round(time.time() - self.start_time)
    print("The bowl is empty!")
这导致了预期的输出,但代价是一些复杂性。但是,如果你准备改变你的方法,有一个简单得多的可能性:让每个吃的人成为一个独立的演员,继续吃,直到碗里没有更多的食物。那么你就不需要“重启”食客,因为他们一开始就不会离开,至少只要碗里有食物:

async def eat_from(self, foodbowl):
    while foodbowl.qty:
        servings_taken = self.serving_size \
            if foodbowl.qty >= self.serving_size else foodbowl.qty
        foodbowl.qty -= servings_taken
        t = round(time.time() - foodbowl.start_time)
        print("(t={}) Person {} picks up {} servings of food, "
              "leaving {} servings in the bowl."
              .format(t, self.name, servings_taken, foodbowl.qty))
        await asyncio.sleep(self.time_to_eat)
assign\u eaters
不再需要循环,而是使用简单的
聚集:

async def assign_eaters(self, eaters):
    self.start_time = time.time()
    await asyncio.gather(*[eater.eat_from(self) for eater in eaters])
    t = round(time.time() - self.start_time)
    print("The bowl is empty!")
这段更简单的代码再次产生了预期的输出。唯一的“缺点”是改变需要反转控制:碗不再驱动进食过程,现在由每个进食者自主完成,碗被动地等待他们完成。然而,从问题陈述来看,这似乎不仅是可以接受的,甚至可能是追求的解决方案。据说,食物碗功能应该让人们“开始从碗里吃东西,直到碗空为止”。“开始吃”意味着碗只是启动了这个过程,每个人都自己吃——这就是第二个版本的工作原理