I'm trying to use Lock in Asyncio to prevent the function method() to run "more that ones at the same time".
My motivation: I use the Asyncio library for asynchronous communication with multiple devices. The issue is, that the devices need some time to respond and if other request is send in the meantime to the same device, error occurs. So I want to built in the class (of the device) a Lock to prevent that.
This code is just very simple way to test the concept and understand it before implementing that to the actual code. BUT even this test code does not work as I expect it. :D
First: If I try to run this code:
import asyncio
async def method(wait:int):
print(f"Method with waiting {wait}s starting")
await asyncio.sleep(wait)
print(f"Method with waiting {wait}s finished")
async def main():
task1 = asyncio.create_task(method(2))
task2 = asyncio.create_task(method(2))
await task1
await task2
asyncio.run(main())
The task1 starts execution, sleeps so the task2 starts. Then they both finish right after each other like this (which is to expect):
Method with waiting 2s starting
Method with waiting 2s starting
Method with waiting 2s finished
Method with waiting 2s finished
Now I want to prevent starting the execution of the task2 before the task1 finish. And the best way (I though) would be with Lock. So I adapted the code:
import asyncio
locker = asyncio.Lock()
async def method(wait:int):
async with locker:
print(f"Method with waiting {wait}s starting")
await asyncio.sleep(wait)
print(f"Method with waiting {wait}s finished")
async def main():
task1 = asyncio.create_task(method(2))
task2 = asyncio.create_task(method(2))
await task1
await task2
asyncio.run(main())
The code does not work as expected and I get following error:
Method with waiting 2s starting
Method with waiting 2s finished
Traceback (most recent call last):
File "/Users/filip/Desktop/Python/07_Asyncio2.py", line 16, in <module>
asyncio.run(main())
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/Users/filip/Desktop/Python/07_Asyncio2.py", line 14, in main
await task2
File "/Users/filip/Desktop/Python/07_Asyncio2.py", line 5, in method
async with locker:
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/locks.py", line 14, in __aenter__
await self.acquire()
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/locks.py", line 120, in acquire
await fut
RuntimeError: Task <Task pending name='Task-3' coro=<method() running at /Users/filip/Desktop/Python/07_Asyncio2.py:5>> got Future <Future pending> attached to a different loop
Running on MacOS, Python version: 3.9.5 Thank you for any help and tips :)
Change code the following way and it will work:
import asyncio
async def method(wait: int, locker: asyncio.Lock):
async with locker:
print(f"Method with waiting {wait}s starting")
await asyncio.sleep(wait)
print(f"Method with waiting {wait}s finished")
async def main():
locker = asyncio.Lock()
await asyncio.gather(method(2, locker), method(2, locker))
if __name__ == '__main__':
asyncio.run(main())
asyncio.Lock
as function argumentasyncio.gather
to run tasks simultaneously, the way you used tasks
you do not need lock at all.The main problem is that your lock and you coroutines belongs to different event loops, so I moved lock to another place.
How to check that loops are different? add line print(id(asyncio.get_event_loop()))
after you lock declaration and at the begin of your main function.