Это потому, что конструктор Semaphore устанавливает свой атрибут _loop
- в asyncio / locks.py :
class Semaphore(_ContextManagerMixin):
def __init__(self, value=1, *, loop=None):
if value < 0:
raise ValueError("Semaphore initial value must be >= 0")
self._value = value
self._waiters = collections.deque()
if loop is not None:
self._loop = loop
else:
self._loop = events.get_event_loop()
Но asyncio.run()
запускает совершенно новый цикл - в asyncio / runners.py , также упоминается в документации:
def run(main, *, debug=False):
if events._get_running_loop() is not None:
raise RuntimeError(
"asyncio.run() cannot be called from a running event loop")
if not coroutines.iscoroutine(main):
raise ValueError("a coroutine was expected, got {!r}".format(main))
loop = events.new_event_loop()
...
Semaphore
, инициированный вне asyncio.run()
, захватывает цикл «по умолчанию» асинхронного доступа и поэтому не может использоваться с циклом событийсоздан с asyncio.run()
.
Решение
Инициируйте Semaphore
из кода, вызванного asyncio.run()
.Вам нужно будет передать их в нужное место, есть больше возможностей, как это сделать, например, вы можете использовать contextvars , но я просто приведу самый простой пример:
import asyncio
async def work(sem):
async with sem:
print('working')
await asyncio.sleep(1)
async def main():
sem = asyncio.Semaphore(2)
await asyncio.gather(work(sem), work(sem), work(sem))
asyncio.run(main())