Async و Await در پایتون چیست؟

در این آموزش با برنامه‌نویسی غیرهمزمان در پایتون آشنا می‌شویم و یاد می‌گیریم چگونه با استفاده از async، await و کتابخانه asyncio چند کار را به شکل کارآمدتری مدیریت کنیم.

برنامه‌نویسی غیرهمزمان چیست؟

در برنامه‌های معمولی، دستورات معمولاً یکی پس از دیگری اجرا می‌شوند. یعنی تا زمانی که یک عملیات تمام نشود، برنامه به مرحله بعدی نمی‌رود.

اما در بعضی برنامه‌ها، مانند برنامه‌هایی که با اینترنت، API، دانلود فایل یا پایگاه داده کار می‌کنند، ممکن است برنامه زمان زیادی را منتظر بماند.

برنامه‌نویسی غیرهمزمان یا Asynchronous Programming به ما کمک می‌کند در زمان انتظار، برنامه بتواند کارهای دیگری را نیز انجام دهد.

async در پایتون چیست؟

کلمه کلیدی async برای تعریف یک تابع غیرهمزمان استفاده می‌شود.

یک نمونه ساده:

async def hello():
    print("Hello Python")

تابعی که با async def تعریف می‌شود، یک coroutine ایجاد می‌کند.

await در پایتون چیست؟

از await برای منتظر ماندن برای پایان یک عملیات غیرهمزمان استفاده می‌کنیم.

برای مثال:

import asyncio

async def hello():
    await asyncio.sleep(2)
    print("Hello Python")

در این مثال برنامه به مدت دو ثانیه منتظر می‌ماند، اما این انتظار می‌تواند به صورت غیرهمزمان مدیریت شود.

کتابخانه asyncio چیست؟

asyncio یکی از کتابخانه‌های استاندارد پایتون برای نوشتن برنامه‌های غیرهمزمان است.

با استفاده از این کتابخانه می‌توانیم coroutineها را اجرا، مدیریت و با یکدیگر هماهنگ کنیم.

import asyncio

async def hello():
    print("Hello Python")

asyncio.run(hello())

تابع asyncio.run() اجرای coroutine اصلی برنامه را آغاز می‌کند.

یک مثال ساده از async و await

فرض کنید می‌خواهیم یک عملیات را شبیه‌سازی کنیم که دو ثانیه زمان می‌برد.

import asyncio

async def task():
    print("شروع عملیات")

    await asyncio.sleep(2)

    print("عملیات تمام شد")


asyncio.run(task())

ابتدا پیام «شروع عملیات» نمایش داده می‌شود. سپس برنامه به مدت دو ثانیه منتظر می‌ماند و در پایان پیام دوم نمایش داده می‌شود.

اجرای معمولی دو کار

ابتدا یک مثال معمولی را ببینیم. در این برنامه دو کار به صورت پشت سر هم انجام می‌شوند:

import time

def task(name):
    print(f"{name} شروع شد")
    time.sleep(2)
    print(f"{name} تمام شد")


start = time.time()

task("کار اول")
task("کار دوم")

print("زمان:", time.time() - start)

در این حالت، کار دوم بعد از تمام شدن کار اول شروع می‌شود.

اجرای چند کار با asyncio

حالا می‌توانیم از asyncio.gather() استفاده کنیم تا چند coroutine را با هم اجرا کنیم.

import asyncio

async def task(name):
    print(f"{name} شروع شد")

    await asyncio.sleep(2)

    print(f"{name} تمام شد")


async def main():

    await asyncio.gather(
        task("کار اول"),
        task("کار دوم")
    )


asyncio.run(main())

در این مثال، دو کار به صورت همزمان در event loop مدیریت می‌شوند و هر دو عملیات انتظار خود را انجام می‌دهند.

asyncio.gather() چیست؟

تابع asyncio.gather() برای اجرای چند coroutine و انتظار برای تکمیل آن‌ها استفاده می‌شود.

await asyncio.gather(
    task1(),
    task2(),
    task3()
)

این روش مخصوصاً زمانی کاربردی است که چند عملیات مستقل داشته باشیم؛ برای مثال ارسال چند درخواست شبکه.

مثال کاربردی: شبیه‌سازی دریافت اطلاعات

فرض کنید می‌خواهیم اطلاعات سه کاربر را دریافت کنیم و هر درخواست دو ثانیه زمان می‌برد.

import asyncio

async def get_user(user_id):

    print(f"دریافت کاربر {user_id}")

    await asyncio.sleep(2)

    return f"User {user_id}"


async def main():

    users = await asyncio.gather(
        get_user(1),
        get_user(2),
        get_user(3)
    )

    print(users)


asyncio.run(main())

در برنامه‌های واقعی، چنین الگویی می‌تواند برای کارهایی مانند درخواست‌های شبکه و ارتباط با APIهای غیرهمزمان مفید باشد.

Coroutine چیست؟

Coroutine نوعی تابع غیرهمزمان است که اجرای آن می‌تواند متوقف شود و بعداً ادامه پیدا کند.

وقتی تابعی را با async def تعریف می‌کنیم، فراخوانی آن مستقیماً مثل یک تابع معمولی اجرا نمی‌شود؛ بلکه یک coroutine object ایجاد می‌کند.

async def hello():
    print("Hello")


result = hello()

print(result)

برای اجرای coroutine باید آن را در محیط مناسب asyncio اجرا کنیم.

Event Loop چیست؟

یکی از مفاهیم مهم در برنامه‌نویسی غیرهمزمان، Event Loop است.

Event Loop وظیفه مدیریت coroutineها و عملیات غیرهمزمان را بر عهده دارد و مشخص می‌کند چه کاری باید اجرا یا ادامه داده شود.

برای شروع یادگیری async در پایتون لازم نیست در ابتدا جزئیات داخلی Event Loop را به صورت عمیق بدانید؛ شناخت مفهوم کلی آن کافی است.

تفاوت برنامه‌نویسی معمولی و غیرهمزمان

برنامه‌نویسی معمولی برنامه‌نویسی غیرهمزمان
دستورات معمولاً پشت سر هم اجرا می‌شوند. چند عملیات انتظارمحور می‌توانند با هم مدیریت شوند.
برای برخی عملیات‌های I/O ممکن است زمان انتظار بیشتری ایجاد شود. در زمان انتظار یک کار، event loop می‌تواند کار دیگری را پیش ببرد.
ساختار ساده‌تری دارد. برای برنامه‌های غیرهمزمان نیاز به مفاهیم بیشتری دارد.

چه زمانی از async استفاده کنیم؟

async معمولاً زمانی مفید است که برنامه با عملیات ورودی و خروجی یا I/O زیادی سروکار داشته باشد.

  • درخواست‌های شبکه
  • ارتباط با APIها
  • دانلود یا ارسال اطلاعات
  • برنامه‌های شبکه‌ای
  • ارتباط غیرهمزمان با برخی سرویس‌ها و پایگاه‌های داده

async به این معنی نیست که هر برنامه‌ای با استفاده از آن سریع‌تر می‌شود. نوع عملیات و ساختار برنامه اهمیت زیادی دارد.

اشتباهات رایج در استفاده از async و await

۱. فراموش کردن await

اگر یک coroutine را بدون await در جای مناسب فراخوانی کنیم، ممکن است coroutine اجرا نشود یا نتیجه مورد انتظار را دریافت نکنیم.

۲. استفاده از await خارج از تابع async

در حالت معمول، دستور await باید در یک محیط غیرهمزمان مناسب استفاده شود.

۳. استفاده از async برای هر کاری

همه برنامه‌ها به async نیاز ندارند. اگر برنامه ساده‌ای دارید که عملیات انتظارمحور زیادی ندارد، استفاده از async ممکن است فقط پیچیدگی کد را بیشتر کند.

تمرین برای شما

یک برنامه بنویسید که سه تابع غیرهمزمان داشته باشد:

  1. دریافت اطلاعات کاربر
  2. دریافت اطلاعات محصولات
  3. دریافت اطلاعات سفارش‌ها

هر تابع باید با async def تعریف شود و برای شبیه‌سازی تأخیر از asyncio.sleep() استفاده کند.

سپس هر سه تابع را با asyncio.gather() اجرا کنید.

جمع‌بندی

در این آموزش با مفاهیم اصلی برنامه‌نویسی غیرهمزمان در پایتون آشنا شدیم.

  • async برای تعریف تابع غیرهمزمان استفاده می‌شود.
  • await برای انتظار غیرهمزمان استفاده می‌شود.
  • asyncio ابزار اصلی استاندارد پایتون برای این مدل برنامه‌نویسی است.
  • asyncio.run() برای اجرای coroutine اصلی استفاده می‌شود.
  • asyncio.gather() امکان مدیریت چند coroutine را فراهم می‌کند.
  • async بیشتر برای برنامه‌های دارای عملیات I/O و انتظار مناسب است.

سوالات متداول

آیا async باعث سریع‌تر شدن همه برنامه‌های پایتون می‌شود؟

خیر. async بیشتر برای مدیریت کارآمد عملیات انتظارمحور، مخصوصاً I/O، کاربرد دارد و برای هر نوع محاسبه‌ای مناسب نیست.

آیا برای یادگیری async باید asyncio را بلد باشم؟

برای شروع، آشنایی با مفاهیم پایه asyncio بسیار مفید است؛ سپس می‌توانید به سراغ کتابخانه‌ها و ابزارهای پیشرفته‌تر بروید.

تفاوت async و await چیست؟

async برای تعریف یک تابع غیرهمزمان استفاده می‌شود، در حالی که await برای منتظر ماندن برای نتیجه یک عملیات غیرهمزمان استفاده می‌شود.

آیا async همان Threading است؟

خیر. Asyncio و Threading دو مدل متفاوت برای مدیریت کارها هستند و نحوه اجرای آن‌ها یکسان نیست.

مطالب مرتبط