import asyncio
import random
WORKERS_COUNT = 10
async def process_url(url: str) -> str:
sleep = round(random.random(), 2)
print("\tStarted", url)
await asyncio.sleep(sleep)
print(f"\tFinished by {sleep} seconds", url)
return url
async def main():
# Эксперимент действительно работает корректно, но это все еще достаточно
# "головоломное" решение
# Очень легко при каком-то рефакторинге или добавлении функционала разломать это,
# особенно, если станет код чуть более похож на реалистичный, а не для эксперимента
# Правильно при подобном подходе с батчевыми воркерами все-же использовать
# asyncio.Queue()
# Александр, еще раз спасибо за интересный пример! :)
urls = [f"url{i}" for i in range(1, 21)]
result = {}
iter_var = 0
async def worker(worker_id: int):
nonlocal iter_var
while True:
try:
print("Worker", worker_id, f"{iter_var=}")
url = urls[iter_var]
# Даже перенос инкремента на после await не ломает слишком сильно,
# только лишние вызовы в конце появляются
iter_var += 1
result[url] = await process_url(url)
except IndexError:
return
await asyncio.gather(*([worker(i) for i in range(WORKERS_COUNT)]))
# CHECK (не должен ничего вывести)
urls_check_set = set()
for k, v in result.items():
if k != v:
print("URL != URL")
if k not in urls_check_set:
urls_check_set.add(k)
if len(urls_check_set) != len(set(urls)):
print(len(urls_check_set))
if urls_check_set != set(urls):
print(urls_check_set.symmetric_difference(set(urls)))
if __name__ == '__main__':
asyncio.run(main())
# CHECK (should not print)
urls_check_set = set()
for k, v in result.items():
if k != v:
print("URL != URL")
if k not in urls_check_set:
urls_check_set.add(k)
if len(urls_check_set) != len(set(urls)):
print(len(urls_check_set))
if urls_check_set != set(urls):
print(urls_check_set.symmetric_difference(set(urls)))
if __name__ == '__main__':
asyncio.run(main())