import asyncio
l = asyncio.Lock()
async def test():
print('locked' if l.locked() else 'unlocked')
await l.acquire()
# await asyncio.ensure_future(l.acquire())
await asyncio.sleep(1)
l.release()
async def main():
await asyncio.gather(test(), test())
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
We start two test()
coroutines, first of them immediately locks Lock by l.acquire()
, second prints locked
state. Output:
unlocked
locked
Everything changes if you will comment await l.acquire()
line and uncomment next one. Output will be:
unlocked
unlocked
It happens because l.acquire()
wrapped in Task starts after second test()
was started.
Is there any way to make l.acquire()
Task to be started as soon as possible, before second test()
(and to get same output as in the original code)?
Looks like I found solution. We need global lock that would suspend second task locked checking while first task starts:
import asyncio
l = asyncio.Lock()
check_lock = asyncio.Lock()
async def test():
async with check_lock:
print('locked' if l.locked() else 'unlocked')
await asyncio.ensure_future(l.acquire())
print('now', 'locked' if l.locked() else 'unlocked')
await asyncio.sleep(1)
l.release()
async def main():
await asyncio.gather(test(), test())
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Output:
unlocked
now locked
locked
# 1 second delay here
now locked
But here we get another problem: second task would be suspended while first task completely done, that's why we get 1 second delay before second locking. It was hard to see in original example (because test()
wait for single lock), that's why I added second printing. But it can be important for multiple locking of different resources to start coroutine immediately: we should lock only creating task, but not it's awaiting. Creating task itself wouldn't start that task (and l.acquire()
) immediately, we should return control to event loop. It can be done by await asyncio.sleep(0)
. Here's final solution for original code:
import asyncio
l = asyncio.Lock()
check_lock = asyncio.Lock()
async def test():
async with check_lock:
print('locked' if l.locked() else 'unlocked')
task = asyncio.ensure_future(l.acquire()) # only create task
await asyncio.sleep(0) # return control to event loop, it allows lock to be locked before task completed
print('now', 'locked' if l.locked() else 'unlocked') # would be printed immediately
await task # and now we can await for task done
await asyncio.sleep(1)
l.release()
async def main():
await asyncio.gather(test(), test())
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Output:
unlocked
now locked
locked
# no delay here
now locked