آشنایی با asyncio در پایتون: برنامه‌نویسی ناهمزمان

آشنایی با asyncio در پایتون: برنامه‌نویسی ناهمزمان

در دنیای مدرن برنامه‌نویسی، توانایی مدیریت همزمان چندین عملیات به طور موثر، امری حیاتی است. پایتون، با ارائه کتابخانه asyncio، امکان برنامه‌نویسی ناهمزمان (Asynchronous Programming) را فراهم می‌کند. این مقاله به بررسی عمیق asyncio، مفاهیم کلیدی آن و نحوه استفاده از آن در پروژه‌های پایتون می‌پردازد. این آموزش در سطح مفاهیم متوسط قرار دارد و فرض بر این است که خواننده با اصول اولیه پایتون آشنایی دارد.

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

در برنامه‌نویسی سنتی (همزمان)، هر عملیات به ترتیب و یکی پس از دیگری انجام می‌شود. اگر یک عملیات زمان‌بر باشد (مانند خواندن از یک فایل بزرگ یا ارسال درخواست به یک سرور خارجی)، کل برنامه تا پایان آن عملیات مسدود می‌شود. این موضوع می‌تواند منجر به کندی و عدم پاسخگویی برنامه شود.

برنامه‌نویسی ناهمزمان به برنامه اجازه می‌دهد تا در حین انتظار برای تکمیل یک عملیات، به انجام عملیات‌های دیگر بپردازد. این کار با استفاده از مفهوم “رویدادها” (Events) و “هم‌روتین‌ها” (Coroutines) انجام می‌شود. به جای مسدود کردن برنامه، عملیات‌های زمان‌بر به عنوان رویداد ثبت می‌شوند و برنامه به انجام کارهای دیگر می‌پردازد. هنگامی که رویداد تکمیل شد، برنامه به آن رویداد باز می‌گردد و ادامه کار را انجام می‌دهد.

مفاهیم کلیدی asyncio

asyncio بر پایه چند مفهوم کلیدی بنا شده است:

  • رویداد حلقه (Event Loop): قلب تپنده asyncio است. رویداد حلقه مسئول نظارت بر رویدادها، زمان‌بندی هم‌روتین‌ها و اجرای آن‌ها است.
  • هم‌روتین (Coroutine): یک تابع ویژه است که می‌تواند به طور موقت متوقف شود و سپس از جایی که متوقف شده بود، ادامه یابد. هم‌روتین‌ها با استفاده از کلمات کلیدی async و await تعریف می‌شوند.
  • آینده (Future): نمایانگر نتیجه یک عملیات ناهمزمان است. یک آینده می‌تواند در حالت “در حال انجام”، “تکمیل شده” یا “لغو شده” باشد.
  • وظیفه (Task): یک شیء است که یک هم‌روتین را در رویداد حلقه زمان‌بندی می‌کند.

تعریف هم‌روتین‌ها با async و await

برای تعریف یک هم‌روتین، از کلمه کلیدی async قبل از تعریف تابع استفاده می‌کنیم. داخل هم‌روتین، می‌توانیم از کلمه کلیدی await برای انتظار برای تکمیل یک عملیات ناهمزمان استفاده کنیم.


async def my_coroutine():
  print("شروع هم‌روتین")
  await asyncio.sleep(1)  # شبیه‌سازی یک عملیات زمان‌بر
  print("پایان هم‌روتین")

در این مثال، asyncio.sleep(1) یک هم‌روتین دیگر است که به مدت 1 ثانیه صبر می‌کند. کلمه کلیدی await باعث می‌شود که هم‌روتین my_coroutine تا زمانی که asyncio.sleep(1) تکمیل شود، متوقف شود. در این مدت، رویداد حلقه می‌تواند به انجام هم‌روتین‌های دیگر بپردازد.

رویداد حلقه و اجرای هم‌روتین‌ها

برای اجرای هم‌روتین‌ها، باید یک رویداد حلقه ایجاد کنیم و سپس آن‌ها را به آن اضافه کنیم.


import asyncio

async def main():
  print("شروع برنامه")
  task1 = asyncio.create_task(my_coroutine())
  task2 = asyncio.create_task(my_coroutine())
  await asyncio.gather(task1, task2) # اجرای همزمان task1 و task2
  print("پایان برنامه")

if __name__ == "__main__":
  asyncio.run(main())

در این مثال، asyncio.run(main()) یک رویداد حلقه ایجاد می‌کند و هم‌روتین main را در آن اجرا می‌کند. داخل main، دو وظیفه (Task) ایجاد می‌کنیم که هر کدام یک نمونه از my_coroutine را اجرا می‌کنند. asyncio.gather(task1, task2) منتظر می‌ماند تا هر دو وظیفه تکمیل شوند.

استفاده از asyncio برای عملیات ورودی/خروجی (I/O)

asyncio به ویژه برای عملیات ورودی/خروجی (مانند خواندن از شبکه، نوشتن در فایل‌ها و تعامل با پایگاه داده) مناسب است. کتابخانه‌های زیادی وجود دارند که از asyncio پشتیبانی می‌کنند و امکان انجام این عملیات‌ها به صورت ناهمزمان را فراهم می‌کنند.

به عنوان مثال، کتابخانه aiohttp امکان ارسال درخواست‌های HTTP به صورت ناهمزمان را فراهم می‌کند.


import asyncio
import aiohttp

async def fetch_url(url):
  async with aiohttp.ClientSession() as session:
    async with session.get(url) as response:
      return await response.text()

async def main():
  url1 = "https://www.example.com"
  url2 = "https://www.google.com"

  task1 = asyncio.create_task(fetch_url(url1))
  task2 = asyncio.create_task(fetch_url(url2))

  result1 = await task1
  result2 = await task2

  print(f"محتوای {url1}: {result1[:100]}...")
  print(f"محتوای {url2}: {result2[:100]}...")

if __name__ == "__main__":
  asyncio.run(main())

در این مثال، fetch_url یک هم‌روتین است که یک URL را دریافت می‌کند و محتوای آن را به صورت ناهمزمان از طریق aiohttp واکشی می‌کند. async with aiohttp.ClientSession() as session: یک جلسه HTTP ناهمزمان ایجاد می‌کند و async with session.get(url) as response: یک درخواست GET به URL ارسال می‌کند. await response.text() منتظر می‌ماند تا پاسخ دریافت شود و سپس محتوای آن را به صورت متن برمی‌گرداند.

مدیریت خطاها در asyncio

همانند برنامه‌نویسی همزمان، مدیریت خطاها در asyncio نیز بسیار مهم است. می‌توانیم از بلوک‌های try...except برای گرفتن استثناها در هم‌روتین‌ها استفاده کنیم.


import asyncio

async def my_coroutine():
  try:
    # کد ممکن است استثنا ایجاد کند
    result = 10 / 0
  except ZeroDivisionError:
    print("خطا: تقسیم بر صفر!")
    return None
  return result

async def main():
  result = await my_coroutine()
  print(f"نتیجه: {result}")

if __name__ == "__main__":
  asyncio.run(main())

در این مثال، اگر تقسیم بر صفر رخ دهد، استثنا ZeroDivisionError گرفته می‌شود و پیام “خطا: تقسیم بر صفر!” چاپ می‌شود.

نکات پیشرفته

asyncio امکانات پیشرفته‌تری نیز دارد، از جمله:

  • هم‌روتین‌های تو در تو: می‌توانیم هم‌روتین‌ها را در داخل هم‌روتین‌های دیگر فراخوانی کنیم.
  • زمان‌بندی وظایف: می‌توانیم وظایف را با استفاده از متدهای مختلف رویداد حلقه زمان‌بندی کنیم.
  • همگام‌سازی: می‌توانیم از ابزارهایی مانند Lock و Semaphore برای همگام‌سازی دسترسی به منابع مشترک استفاده کنیم.

نتیجه‌گیری

asyncio یک ابزار قدرتمند برای برنامه‌نویسی ناهمزمان در پایتون است. با استفاده از asyncio، می‌توانیم برنامه‌هایی بنویسیم که پاسخگوتر، کارآمدتر و مقیاس‌پذیرتر باشند. این کتابخانه به ویژه برای برنامه‌هایی که با عملیات ورودی/خروجی زیادی سروکار دارند، مفید است. با درک مفاهیم کلیدی asyncio و نحوه استفاده از آن، می‌توانید برنامه‌های پایتون خود را به سطح جدیدی از عملکرد و کارایی برسانید.

بدون دیدگاه

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

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *