آموزش ماژول asyncio – متد as_completed

Please login to bookmark Close

مشکل: نتیجه‌ها به ترتیبِ await می‌آیند، نه به ترتیبِ آماده‌شدن

فرض کنید چند کار را هم‌زمان اجرا کرده‌ایم و می‌خواهیم به‌محض آماده‌شدنِ هر نتیجه از آن استفاده کنیم. اگر تسک‌ها را یکی‌یکی await کنیم، نتیجه‌ها فقط به همان ترتیبی که await کرده‌ایم در دسترس قرار می‌گیرند — حتی اگر تسکِ دیگری زودتر تمام شده باشد. به کد زیر توجه کنید:

import asyncio

async def download_file(file_name, weight):
    print(f'download {file_name} started')
    await asyncio.sleep(weight)
    print(f'download {file_name} finished')
    return file_name

async def manage_downloads():
    t1 = asyncio.create_task(download_file('test1.txt', 1))
    t2 = asyncio.create_task(download_file('test2.txt', 2))

    print(await t1)
    print(await t2)

asyncio.run(manage_downloads())

اینجا t1 زودتر (۱ ثانیه) تمام می‌شود، پس خروجی طبیعی به‌نظر می‌رسد:

download test1.txt started
download test2.txt started
download test1.txt finished
test1.txt
download test2.txt finished
test2.txt

حالا وزن‌ها را جابه‌جا می‌کنیم تا t2 زودتر تمام شود:

import asyncio

async def download_file(file_name, weight):
    print(f'download {file_name} started')
    await asyncio.sleep(weight)
    print(f'download {file_name} finished')
    return file_name

async def manage_downloads():
    t1 = asyncio.create_task(download_file('test1.txt', 2))
    t2 = asyncio.create_task(download_file('test2.txt', 1))

    print(await t1)
    print(await t2)

asyncio.run(manage_downloads())

خروجی:

download test1.txt started
download test2.txt started
download test2.txt finished
download test1.txt finished
test1.txt
test2.txt

می‌بینید که با اینکه t2 زودتر تمام شده، باز هم اول نتیجه‌ی t1 چاپ می‌شود و بعد t2؛ فقط به این دلیل که اول t1 را await کرده‌ایم. یعنی نمی‌توانیم نتیجه‌ی زودرسِ t2 را زودتر مصرف کنیم.

راه‌حل: as_completed

متد asyncio.as_completed این امکان را می‌دهد که بین همه‌ی تسک‌ها، هرکدام که زودتر تمام شد، نتیجه‌اش را زودتر برداریم. این تابع روی مجموعه‌ای از تسک‌ها یک iterator می‌سازد که به ترتیبِ «اتمام» (نه ترتیب ورودی) نتیجه‌ها را می‌دهد:

import asyncio

async def download_file(file_name, weight):
    print(f'download {file_name} started')
    await asyncio.sleep(weight)
    print(f'download {file_name} finished')
    return file_name

async def manage_downloads():
    t1 = asyncio.create_task(download_file('test1.txt', 2))
    t2 = asyncio.create_task(download_file('test2.txt', 1))

    for task in asyncio.as_completed([t1, t2]):
        print(await task)          # yields whichever finishes first

asyncio.run(manage_downloads())

حالا خروجی به ترتیب اتمام است؛ چون t2 زودتر تمام می‌شود، نتیجه‌اش هم زودتر مصرف می‌شود:

download test1.txt started
download test2.txt started
download test2.txt finished
test2.txt
download test1.txt finished
test1.txt

مثال واقعی: پرس‌وجو از چند سرور و پردازش تدریجی نتایج

فرض کنید همان داده را از چند سرور (mirror) درخواست می‌کنید و می‌خواهید به‌محض رسیدن هر پاسخ، آن را پردازش یا نمایش دهید — نه اینکه تا رسیدن کندترین پاسخ منتظر بمانید. این دقیقاً همان چیزی است که as_completed فراهم می‌کند و در نوار پیشرفت، نمایش نتایج جست‌وجو، یا جمع‌آوری زودهنگام داده بسیار کاربردی است:

import asyncio
import random

async def fetch(server_id):
    delay = random.uniform(0.5, 3)      # each server responds at a different time
    await asyncio.sleep(delay)
    return f"server {server_id} responded in {delay:.1f}s"

async def main():
    tasks = [asyncio.create_task(fetch(i)) for i in range(5)]
    # handle each response as soon as it arrives
    for task in asyncio.as_completed(tasks):
        result = await task
        print("received:", result)

asyncio.run(main())

نتایج به‌ترتیبِ سرعت پاسخ‌دهی سرورها چاپ می‌شوند، نه به‌ترتیب شماره‌ی سرور. اگر فقط سریع‌ترین پاسخ برایتان کافی است، می‌توانید بعد از اولین نتیجه از حلقه خارج شوید (و در صورت نیاز بقیه‌ی تسک‌ها را کنسل کنید).

نکته: as_completed یک پارامتر timeout هم می‌گیرد؛ اگر در آن مدت همه‌ی تسک‌ها تمام نشوند، هنگام await کردنِ نتیجه‌ی باقی‌مانده، TimeoutError پرتاب می‌شود.

as_completed در برابر gather

تفاوت مهم: asyncio.gather منتظر می‌ماند تا همه تمام شوند و نتیجه‌ها را یک‌جا و به ترتیب ورودی برمی‌گرداند. اما as_completed نتیجه‌ها را یکی‌یکی و به ترتیب اتمام می‌دهد تا بتوانید هر نتیجه را به‌محض آماده‌شدن پردازش کنید. اگر نتایجِ همه را با هم و مرتب می‌خواهید از gather، و اگر می‌خواهید هر نتیجه را زودتر مصرف کنید از as_completed استفاده کنید.

جمع‌بندی

  • await کردنِ ترتیبیِ تسک‌ها، نتیجه‌ها را فقط به ترتیب await می‌دهد، نه به ترتیب آماده‌شدن.
  • asyncio.as_completed([...]) نتیجه‌ها را به ترتیب اتمام می‌دهد؛ پس می‌توانید هر نتیجه را به‌محض آماده‌شدن مصرف کنید.
  • کاربرد واقعی: پردازش تدریجی نتایج، نمایش پیشرفت، یا استفاده از سریع‌ترین پاسخ بین چند سرور.
  • برای گرفتنِ همه‌ی نتایج به‌صورت مرتب و یک‌جا از gather، و برای مصرف زودهنگام از as_completed استفاده کنید.

Please login to bookmark Close
پیشرفت شما در «دوره آموزش کانکارنسی در پایتون» (50%)
نظرات

دیدگاهتان را بنویسید

50%
پیشرفت

سرفصل دوره

فهرست مطالب

سرفصل دوره

تمرین

این قسمت تمرین ندارد!

پاسخ تمرین ها

هنوز برای تمرین‌های این قسمت پاسخی ثبت نشده است!

اشتراک گذاری

چرا بهتره از فیلترشکن استفاده کنید؟

من همه ویدئو ها و پادکست های کُدباز رو توی یوتیوب و ساندکلود و پلتفرم هایی آپلود می‌کنم که اغلب فیلتر هستند.

اغلب آموزش‌ها ویدئو و پادکست دارند. پس اگر می‌خواهید از محتوای سایت بیشترین استفاده رو ببرید نیاز به فیلتر شکن دارید.

توجه داشته باشید که برای خرید از فروشگاه بهتره فیلتر شکن رو خاموش کنید.

تنظیمات

انتخاب زبان
تغییر تم