آموزش ماژول asyncio – مروری بر coroutine

Please login to bookmark Close

coroutine چیست؟

هم‌روال (coroutine) نوعی تابع است که معمولاً حداقل یک درخواست به منابع خارجی دارد و پاسخ‌گرفتن به آن درخواست زمان قابل‌توجهی طول می‌کشد (مثل فراخوانی یک API یا خواندن از شبکه). ویژگی خاص coroutine این است که می‌تواند در میانه‌ی اجرا «مکث» کند، کنترل را به دیگری بدهد، و بعداً از همان نقطه ادامه یابد.

چرا باید از coroutine استفاده کنیم؟

فرض کنید یک تابع معمولی ۲۰ خطی دارید. نمی‌توانید اجرای آن را در خط ۱۰ متوقف کنید، تابع دیگری را اجرا کنید و بعد از خط ۱۱ ادامه دهید. اما coroutine دقیقاً همین امکان را می‌دهد. حالا چرا اصلاً باید در اجرای یک تابع وقفه ایجاد کرد؟

اگر فراخوانی یک API که پاسخش چند ثانیه طول می‌کشد را در یک تابع معمولی انجام دهید، در همان نقطه ترد «قفل» می‌شود و بی‌کار منتظر پاسخ می‌ماند. اما اگر همان کار را در یک coroutine انجام دهید، ترد به کمک event loop یک تسک دیگر را برای اجرا برمی‌دارد؛ در نتیجه تا وقتی پاسخ API آماده شود، ترد به‌جای قفل‌شدن، مشغول اجرای کارهای دیگر است. این همان راز کارایی asyncio است.

تعریف coroutine

یک coroutine کاملاً شبیه تابع است، با دو تفاوت: با async def تعریف می‌شود و حداقل یک دستور await در بدنه‌اش دارد.

import asyncio

async def function_name():
    await asyncio.sleep(1)

برای شبیه‌سازی زمان‌بربودن یک عملیات از asyncio.sleep استفاده کرده‌ایم. دقت کنید که نمی‌توان از time.sleep استفاده کرد، چون time.sleep «awaitable» نیست و کل حلقه را بلاک می‌کند.

چطور می‌توان یک coroutine را اجرا کرد؟

دقت کنید که یک coroutine را نمی‌توان مثل یک تابع معمولی و با صداکردن مستقیم اجرا کرد:

import asyncio

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

download_file('test.txt')   # does NOT run; just creates a coroutine object

اجرای کد بالا این هشدار را می‌دهد و تابع اصلاً اجرا نمی‌شود:

RuntimeWarning: coroutine 'download_file' was never awaited

برای اجرای یک coroutine سه روش داریم.

۱. asyncio.run

این روش یک coroutine را اجرا می‌کند. اما توجه کنید اگر آن را چند بار پشت‌سرهم صدا بزنید، coroutineها به‌صورت ترتیبی (نه هم‌زمان) اجرا می‌شوند:

import asyncio

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

asyncio.run(download_file('test1.txt'))
asyncio.run(download_file('test2.txt'))

خروجی نشان می‌دهد که کارها هم‌زمان اجرا نشده‌اند (کل کار حدود ۴ ثانیه طول می‌کشد):

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

این دقیقاً چیزی نیست که از asyncio می‌خواهیم، چون هدف ما اجرای هم‌زمان درخواست‌هاست.

۲. asyncio.create_task + asyncio.run (روش پیشنهادی)

در این روش هر coroutine را داخل یک coroutine اصلی به‌صورت یک Task اجرا می‌کنیم تا کارها واقعاً هم‌زمان پیش بروند:

import asyncio

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

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

asyncio.run(manage_downloads())

این بار هر دو دانلود با هم شروع می‌شوند و کل کار حدود ۲ ثانیه (نه ۴ ثانیه) طول می‌کشد:

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

تفاوت کلیدی: create_task بلافاصله کار را در حلقه ثبت و شروع می‌کند؛ await فقط برای منتظرماندنِ نتیجه است، نه شروع کار. به همین دلیل هر دو Task پیش از اینکه منتظرشان بمانیم، هم‌زمان در حال اجرا هستند.

۳. asyncio.new_event_loop + run_until_complete

در این روش خودمان event loop را می‌سازیم و اجرا می‌کنیم. این روش سطح‌پایین‌تر است و معمولاً فقط وقتی لازم می‌شود که به کنترل دقیق روی حلقه نیاز داشته باشیم:

import asyncio

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

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

loop = asyncio.new_event_loop()
try:
    loop.run_until_complete(manage_downloads())
finally:
    loop.close()

مثال – دانلود هم‌زمان چند فایل

در عمل، الگوی متداول این است که چند کار I/O را با create_task بسازیم و همه را با هم اجرا کنیم. مثال زیر دانلود پنج فایل را شبیه‌سازی می‌کند؛ به‌جای اینکه ۵ برابرِ زمانِ یک دانلود طول بکشد، تقریباً به‌اندازه‌ی یک دانلود طول می‌کشد:

import asyncio
import time

async def download_file(file_name):
    print(f'download {file_name} started')
    await asyncio.sleep(2)          # in real code: an async network request (e.g. aiohttp)
    print(f'download {file_name} finished')
    return file_name

async def main():
    start = time.time()
    files = [f'file-{i}.zip' for i in range(5)]
    tasks = [asyncio.create_task(download_file(f)) for f in files]
    results = await asyncio.gather(*tasks)
    print("downloaded:", results, "in %.1fs" % (time.time() - start))   # ~2s, not ~10s

asyncio.run(main())

در دنیای واقعی به‌جای asyncio.sleep، یک درخواست شبکه‌ی غیرمسدودکننده (مثلاً با aiohttp) قرار می‌گیرد؛ ساختار کد همین است.

جمع‌بندی

  • coroutine با async def تعریف می‌شود و می‌تواند وسط اجرا مکث کند و کنترل را به حلقه بدهد.
  • صداکردن مستقیم یک coroutine آن را اجرا نمی‌کند و فقط یک شیء coroutine می‌سازد (هشدار «never awaited»).
  • سه روش اجرا: asyncio.run (ساده/ترتیبی)، create_task + run (پیشنهادی و هم‌زمان)، و new_event_loop + run_until_complete (سطح‌پایین).
  • برای اجرای واقعیِ هم‌زمان، کارها را با create_task بسازید و بعد await کنید (یا از gather استفاده کنید).

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

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

43%
پیشرفت

سرفصل دوره

فهرست مطالب

سرفصل دوره

تمرین

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

پاسخ تمرین ها

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

اشتراک گذاری

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

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

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

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

تنظیمات

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