آموزش کامل async و await با مثال‌های C# و JavaScript

برنامه نویسی غیرهمزمان، async await، برنامه نویسی 1404/6/18
نویسنده: مدرس بهمن آبادی

آموزش کامل async و await با مثال‌های C# و JavaScript

Asynchronous

مقدمه

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

دو کلیدواژه‌ی اصلی برای مدیریت این عملیات‌ها عبارتند از:

  • async : مشخص می‌کند که یک متد یا تابع، غیرهمزمان است.

  • await : مشخص می‌کند که اجرای کد باید منتظر تکمیل یک عملیات غیرهمزمان بماند.

بخش اول: async و await در C#

تعریف ساده

در سی‌شارپ، وقتی متدی را با async تعریف می‌کنیم، آن متد معمولاً یک Task یا Task<T> برمی‌گرداند.

مثال ساده

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine("شروع برنامه...");

        string result = await DownloadDataAsync();

        Console.WriteLine("نتیجه دریافت شد:");
        Console.WriteLine(result);

        Console.WriteLine("برنامه تمام شد.");
    }

    static async Task<string> DownloadDataAsync()
    {
        await Task.Delay(2000); // شبیه‌سازی یک عملیات زمان‌بر
        return "داده‌های نمونه بعد از ۲ ثانیه";
    }
}

 

خروجی:

شروع برنامه...
(۲ ثانیه مکث)
نتیجه دریافت شد:
داده‌های نمونه بعد از ۲ ثانیه
برنامه تمام شد.

اجرای چند عملیات همزمان

static async Task Main(string[] args)
{
    var task1 = DownloadDataAsync("لینک ۱");
    var task2 = DownloadDataAsync("لینک ۲");

    var results = await Task.WhenAll(task1, task2);

    foreach (var res in results)
        Console.WriteLine(res);
}

static async Task<string> DownloadDataAsync(string url)
{
    await Task.Delay(1000);
    return $"داده دریافت شد از {url}";

}

ویژگی‌ها در C#

  • در C#، متدهای async معمولاً نوع بازگشتی Task یا Task<T> دارند.
  • برای عملیات زمان‌بر مثل درخواست HTTP یا خواندن فایل مناسب است.

نکات مهم در C#:

  • همیشه متدهای async باید Task یا Task<T> برگردانند، مگر در موارد خاص مثل event handlerها که void مجاز است.
  • از await فقط در متدهای async می‌توانید استفاده کنید.

برنامه نویسی غیرهمزمان

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

کلمه کلیدی async برای متدهایی استفاده می‌شود که شامل عملیات ناهمگام (asynchronous) هستند، یعنی عملیاتی که ممکن است زمان‌بر باشند و نباید رشته (thread) اصلی را مسدود کنند. این عملیات معمولاً شامل موارد زیر هستند:

  • ورودی/خروجی (I/O): مثل درخواست‌های HTTP، خواندن/نوشتن فایل، یا دسترسی به دیتابیس.
  • عملیات زمان‌بر: مثل تأخیر (Task.Delay)، پردازش‌های سنگین در سرور، یا انتظار برای پاسخ از API.
  • کار با APIهای ناهمگام: متدهایی که از کتابخانه‌هایی مثل HttpClient یا Entity Framework استفاده می‌کنند و خودشان به‌صورت ناهمگام طراحی شده‌اند.

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

  1. متدهای محاسباتی ساده (CPU-bound): اگر متد شما فقط شامل محاسبات синхронный (مثل حلقه‌ها یا عملیات ریاضی) است و نیازی به انتظار برای عملیات I/O ندارد، استفاده از async معمولاً غیرضروری و حتی مضر است، چون سربار اضافی (overhead) ایجاد می‌کند. مثال نامناسب:

public async Task<int> CalculateSumAsync(int a, int b)
{
    return a + b; // این عملیات نیازی به async ندارد
}

 

  • این متد هیچ عملیات ناهمگامی ندارد، پس async فقط کد را پیچیده‌تر می‌کند.
  • متدهایی که خیلی سریع اجرا می‌شوند: اگر عملیات شما بسیار سریع است (مثلاً دسترسی به حافظه یا محاسبات سبک)، استفاده از async می‌تواند عملکرد را بدتر کند، چون مدیریت Task و context switching هزینه‌بر است.

مشکلات استفاده غیرضروری از async

  • سربار عملکردی: استفاده از async باعث ایجاد سربار در مدیریت Taskها و state machine در کامپایلر می‌شود، که برای عملیات ساده غیرضروری است.
  • پیچیدگی کد: کد async پیچیده‌تر از کد سینکرونوس است و اگر بدون دلیل استفاده شود، خوانایی و نگهداری کد را سخت‌تر می‌کند.
  • خطاهای احتمالی: استفاده نادرست از async می‌تواند منجر به مشکلاتی مثل deadlock یا مدیریت نادرست exceptionها شود.

نکات کلیدی برای تصمیم‌گیری

  1. نوع عملیات را بررسی کنید:
    • اگر متد شامل عملیات I/O یا انتظار (مثل await Task.Delay) است، از async استفاده کنید.
    • اگر متد فقط محاسباتی است، معمولاً نیازی به async نیست مگر اینکه بخواهید آن را به‌صورت غیرمسدودکننده در پس‌زمینه اجرا کنید (مثل Task.Run).
  2. نوع بازگشتی متد:
    • متدهای async معمولاً باید Task یا Task<T> برگردانند. اگر متد شما void است و نیازی به انتظار ندارد، احتمالاً نیازی به async نیست (به جز در موارد خاص مثل event handlerها).
    • استثنا: متدهای async void فقط برای event handlerها توصیه می‌شوند، چون نمی‌توان آن‌ها را await کرد.
  3. مدیریت منابع: در برنامه‌های با منابع محدود (مثل اپلیکیشن‌های موبایل)، استفاده غیرضروری از async می‌تواند مصرف منابع را افزایش دهد.

    بخش دوم: async و await در JavaScript

    تعریف ساده

    در جاوااسکریپت، متدهایی که async باشند همیشه یک Promise برمی‌گردانند. با await می‌توان نتیجه‌ی یک Promise را گرفت.

    مثال ساده

    async function fetchData() {
      console.log("شروع برنامه...");

      let result = await new Promise(resolve => {
        setTimeout(() => resolve("داده‌های نمونه بعد از ۲ ثانیه"), 2000);
      });

      console.log("نتیجه دریافت شد:");
      console.log(result);

      console.log("برنامه تمام شد.");
    }

    fetchData();

    خروجی:

    شروع برنامه...
    (۲ ثانیه مکث)
    نتیجه دریافت شد:
    داده‌های نمونه بعد از ۲ ثانیه

    برنامه تمام شد.

    اجرای چند عملیات همزمان

    async function fetchMultiple() {
      let task1 = new Promise(resolve => setTimeout(() => resolve("داده لینک ۱"), 1000));
      let task2 = new Promise(resolve => setTimeout(() => resolve("داده لینک ۲"), 1500));

      let results = await Promise.all([task1, task2]);

      console.log(results);
    }

    fetchMultiple();

     

    خروجی:

    [ 'داده لینک ۱', 'داده لینک ۲' ]

    نکات کاربردی برای هر دو زبان

    1. مدیریت خطا: همیشه خطاها رو با try-catch مدیریت کنید تا برنامه پایدار بمونه.
    2. اجتناب از مسدود کردن: از متدهای ناهمگام (async) به جای متدهای همگام (مثل Thread.Sleep در C# یا حلقه‌های طولانی در JS) استفاده کنید.
    3. موازی‌سازی: برای اجرای چند عملیات ناهمگام، از Task.WhenAll (C#) یا Promise.all (JS) استفاده کنید.
    4. خوانایی کد: کدهای async رو ساده و خوانا نگه دارید و از پیچیدگی بیش از حد پرهیز کنید.

    جمع‌بندی

    • در C#، از async و await برای مدیریت عملیات زمان‌بر مثل درخواست‌های HTTP یا I/O استفاده کنید. نوع بازگشتی معمولاً Task است.
    • در JavaScript، از async/await برای کار با Promiseها (مثل fetch) استفاده کنید تا کد خواناتر و مدیریت عملیات ناهمگام ساده‌تر شود.
    • با تمرین مثال‌های بالا و ساخت پروژه‌های کوچک، می‌تونید تسلط خوبی به این مفاهیم پیدا کنید.