我正在使用以下问题在Python中进行异步编程:
模拟多个人在同一个食物碗中进食一定数量的食物。每个人一次可以食用x份食物,然后将其咀嚼y秒钟(模拟了通话阻塞)。只要碗里还有食物,一个人就可以独立于他人咀嚼食物。
为每个食者和饭碗定义班级。最终目标是在食品碗类中具有一个功能,该功能可以接受一系列人员,并让他们从碗中开始进食,直到碗空为止。每当有人从碗里拿食物时,都应该在stdout上打印一条消息。
例如,如果我有一个盛有25份食物的食物碗,并且三个人分别是A,B和C:
因此,预期输出(打印到标准输出)应该是:
(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
之类的两个人准备再吃另一份的时候,顺序并不重要)
该代码是我的尝试:
import asyncio
import time
class Person():
def __init__(self, name, serving_size, time_to_eat):
self.name = name
self.serving_size = serving_size
self.time_to_eat = time_to_eat
async def eat_from(self, foodbowl):
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)
return servings_taken
class FoodBowl():
def __init__(self, qty):
self.qty = qty
async def assign_eaters(self, eaters):
self.start_time = time.time()
while self.qty > 0:
await asyncio.gather(*[eater.eat_from(self) for eater in eaters])
t = round(time.time() - self.start_time)
print("The bowl is empty!")
bowl = 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!
可以看出,每个人都在等每个人吃完饭后再伸手去拿碗。查看我的代码,我知道这是因为我等待着asyncio.gather()
的eat功能,因此它将在任何人重新开始进食之前等待所有三个人完成进食。 >
我知道这是错误的,但是我不知道我可以在asyncio
库中使用什么来解决此问题。我在想eat_from
协程会自动重启,只要碗里还有食物。我该如何完成此工作,或者有解决此问题的更好方法?
答案 0 :(得分:1)
我知道[等待所有三个人吃完饭,然后任何人才能再次开始进食]是错误的,但是我不知道我可以在asyncio库中使用什么来解决这个问题。
您可以使用wait(return_when=asyncio.FIRST_COMPLETED)
等待任何一个吃完,而不必像当前代码那样等待所有全部。只要食者完成进食,就为该食者生成一个新的协程,有效地“重新启动”它。这需要引用wait
返回给食者的任务;这样的引用可以很容易地附加到Task
对象上。代码看起来像这样:
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_eaters
不再需要循环并恢复为使用简单的gather
:
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!")
此更简单的代码再次产生预期的输出。唯一的“缺点”是更改需要反向控制:碗不再驱动进餐过程,现在由每个食者自动完成,而碗被动地等待他们完成。然而,从问题陈述来看,这似乎不仅可以接受,而且甚至可以追捧。据说,饭碗功能应该使人们“从饭碗开始进食,直到饭碗空了”。 “开始进食”意味着碗只是开始烹饪过程,每个人都自己做饭-第二个版本就是这样。