我正在使用Python的asyncio
模块编写一个项目,并且我想使用其同步原语来同步我的任务。但是,它的行为似乎不符合我的预期。
从文档中看来,Condition.wait_for()
提供了一种方法,允许协程等待特定的用户定义条件评估为true。但是,在尝试使用该方法时,它的行为似乎是我无法预期的-我的条件仅被检查了一次,并且如果发现它是假的,等待的任务将永远挂起,而无需再次检查。我在下面写了一个简短的例子来说明我要做什么:
#!/usr/bin/env python
import asyncio
thing = False
setter_done = None
getter_done = None
async def main():
setter_done = asyncio.Event()
getter_done = asyncio.Event()
setter = asyncio.ensure_future(set_thing())
getter = asyncio.ensure_future(get_thing())
#To avoid the loop exiting prematurely:
await setter_done.wait()
await getter_done.wait()
async def set_thing():
global thing
global setter_done
thing = False
#sleep for some arbitrary amount of time; simulate work happening
await asyncio.sleep(10)
thing = True
print("Thing was set to True!")
setter_done.set()
async def get_thing():
global thing
global getter_done
def check_thing():
print("Checking...")
return thing
c = asyncio.Condition()
await c.acquire()
await c.wait_for(check_thing)
c.release()
print("Thing was found to be true!")
getter_done.set()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
我希望它会打印如下内容:
Checking...
Thing was set to True!
Checking...
Thing was found to be True!
相反,我得到:
Checking...
Thing was set to True!
... (hangs indefinitely)
答案 0 :(得分:3)
我发布了一个带有很多评论的完整答案,以帮助解决类似问题的人。我已将代码示例更改为使用类而不是全局变量。有点长,但我希望它不会太复杂。
基本上 Command
类代表一个任务。它是异步的,所以它可以做很多事情。在我的例子中,我只创建了两个虚拟命令(读取“两个任务”),一个暂停 5 秒,一个暂停 8 秒,然后我等待它们都结束并出现条件。显然,条件不是做我所做的事情的唯一方法,但为了与原始答案保持一致,我认为提供一个完整的示例很有趣。就这样吧!
import asyncio
from typing import Set
class Command:
"""A command, an asynchronous task, imagine an asynchronous action."""
async def run(self):
"""To be defined in sub-classes."""
pass
async def start(self, condition: asyncio.Condition,
commands: Set['Command']):
"""
Start the task, calling run asynchronously.
This method also keeps track of the running commands.
"""
commands.add(self)
await self.run()
commands.remove(self)
# At this point, we should ask the condition to update
# as the number of running commands might have reached 0.
async with condition:
condition.notify()
class Look(Command):
"""A subclass of a command, running a dummy task."""
async def run(self):
print("Before looking...")
await asyncio.sleep(5)
print("After looking")
class Scan(Command):
"""A subclass of a command, running a dummy task."""
async def run(self):
print("Before scanning...")
await asyncio.sleep(8)
print("After scanning")
async def main():
"""Our main coroutine, starting commands."""
condition = asyncio.Condition()
commands = set()
commands.add(Look())
commands.add(Scan())
asyncio.gather(*(cmd.start(condition, commands) for cmd in commands))
# Wait for the number of commands to reach 0
async with condition:
await condition.wait_for(lambda: len(commands) == 0)
print("There's no running command now, exiting.")
asyncio.run(main())
所以在实践中(像往常一样,从头开始),我们将 main
称为协程。在 main
中,我们创建了两个命令 Look
和 Scan
,并调用它们的 start
方法。 start
方法定义在每个命令上,它基本上负责在命令运行前将命令本身写入集合,并在运行后(即完全完成后)将其删除。然后它应该通知条件再次检查命令的长度。当没有命令剩下时,程序结束。如果您运行此脚本(我使用 Python 3.8 运行它),您应该看到如下内容:
Before scanning...
Before looking...
After looking
After scanning
There's no running command now, exiting.
请注意,这两个命令同时开始(好吧,Look
开始的时间稍早,事实上,但 Scan
开始于 Look
完成之前)。但是 Look
确实在 Scan
之前结束(大约 3 秒)。直到两个命令都完成后,我们的条件才会被检查。
可以使用事件、锁或信号量来代替吗?可能,但我喜欢在那个例子中使用一个条件。您无需进行大量修改即可轻松完成更多任务。
答案 1 :(得分:2)
将事情设置为true后,您需要添加c.notify_all()
。条件只有在被“通知”后才检查其谓词,否则不会发生任何事情。