← بازگشت

سوالات مصاحبه Async/Await در .NET

مقدمه

برنامه‌نویسی ناهمزمان (Asynchronous Programming) با استفاده از کلمات کلیدی async و await یکی از مهمترین ویژگی‌های مدرن .NET است که به توسعه‌دهندگان امکان می‌دهد برنامه‌های پاسخگو و مقیاس‌پذیر بسازند. این قابلیت برای بهبود تجربه کاربری در برنامه‌های UI و افزایش Throughput در برنامه‌های سرور حیاتی است. این مجموعه شامل 30 سوال تخصصی در مورد Async/Await است که در مصاحبه‌های کاری پرسیده می‌شوند.

سوال 1: Async/Await چیست و چرا از آن استفاده می‌کنیم؟

پاسخ: async و await کلمات کلیدی در C# هستند که برای ساده‌سازی برنامه‌نویسی ناهمزمان معرفی شده‌اند. آن‌ها به شما اجازه می‌دهند کدی بنویسید که به نظر می‌رسد همزمان (Synchronous) است، اما در واقع به صورت ناهمزمان اجرا می‌شود و از بلاک شدن thread اصلی جلوگیری می‌کند.

چرا از Async/Await استفاده می‌کنیم؟

  1. پاسخگویی UI (UI Responsiveness):
    • در برنامه‌های دسکتاپ (WPF, WinForms) یا موبایل، اگر عملیات‌های طولانی (مانند فراخوانی API، دسترسی به دیتابیس، عملیات فایل) را به صورت همزمان در UI thread اجرا کنید، رابط کاربری “فریز” (Freeze) می‌شود و کاربر نمی‌تواند با آن تعامل کند. async/await این مشکل را حل می‌کند.
  2. مقیاس‌پذیری سرور (Server Scalability):
    • در برنامه‌های سرور (ASP.NET Core, Web API)، عملیات‌های I/O-bound (مانند فراخوانی سرویس‌های خارجی یا دیتابیس) بخش عمده‌ای از زمان اجرا را تشکیل می‌دهند. اگر این عملیات‌ها به صورت همزمان اجرا شوند، thread‌های سرور در حین انتظار برای پاسخ بلاک می‌شوند و نمی‌توانند درخواست‌های جدید را پردازش کنند. این امر باعث کاهش Throughput و مقیاس‌پذیری می‌شود.
    • با async/await، thread در حین انتظار آزاد می‌شود و می‌تواند درخواست‌های دیگر را پردازش کند، که منجر به استفاده بهینه‌تر از منابع سرور و افزایش تعداد درخواست‌های همزمان قابل پردازش می‌شود.
  3. ساده‌سازی کد ناهمزمان:
    • قبل از async/await، برنامه‌نویسی ناهمزمان با Callbacks، Task Parallel Library (TPL) یا Event-based Asynchronous Pattern (EAP) پیچیده و مستعد خطا بود. async/await کد ناهمزمان را خواناتر و قابل نگهداری‌تر می‌کند.

مثال عملی:

// بدون Async/Await (بلاک کننده)
public string DownloadContentSync(string url)
{
    using (var client = new HttpClient())
    {
        // این خط UI thread را بلاک می‌کند
        string content = client.GetStringAsync(url).Result; 
        return content;
    }
}

// با Async/Await (غیر بلاک کننده)
public async Task<string> DownloadContentAsync(string url)
{
    using (var client = new HttpClient())
    {
        // thread آزاد می‌شود و به کار دیگری می‌پردازد
        string content = await client.GetStringAsync(url); 
        return content;
    }
}

// نحوه فراخوانی در UI (مثلاً در یک دکمه)
private async void Button_Click(object sender, EventArgs e)
{
    // UI همچنان پاسخگو خواهد بود
    string data = await DownloadContentAsync("http://example.com");
    MessageBox.Show(data);
}

// نحوه فراخوانی در Console (یا Backend)
public static async Task Main(string[] args)
{
    Console.WriteLine("Starting download...");
    string data = await DownloadContentAsync("http://example.com");
    Console.WriteLine("Download complete.");
    Console.WriteLine(data.Substring(0, 50));
}

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

سوال 2: تفاوت بین Task.Run() و await چیست؟ چه زمانی از هر کدام استفاده می‌کنیم؟

پاسخ: هر دو Task.Run() و await در برنامه‌نویسی ناهمزمان نقش دارند، اما برای سناریوهای متفاوتی استفاده می‌شوند و تفاوت‌های اساسی در نحوه مدیریت thread‌ها و اجرای کد دارند.

await:

مثال await:

public async Task<string> DownloadDataAsync(string url)
{
    Console.WriteLine($"Thread ID before await: {Thread.CurrentThread.ManagedThreadId}");
    using (var client = new HttpClient())
    {
        // thread آزاد می‌شود
        string data = await client.GetStringAsync(url);
        Console.WriteLine($"Thread ID after await: {Thread.CurrentThread.ManagedThreadId}");
        return data;
    }
}

Task.Run():

مثال Task.Run():

public async Task<long> CalculateFactorialAsync(int number)
{
    Console.WriteLine($"Thread ID before Task.Run: {Thread.CurrentThread.ManagedThreadId}");
    // عملیات CPU-bound را به Thread Pool می‌فرستد
    long result = await Task.Run(() => 
    {
        Console.WriteLine($"Thread ID inside Task.Run: {Thread.CurrentThread.ManagedThreadId}");
        long factorial = 1;
        for (int i = 1; i <= number; i++)
        {
            factorial *= i;
        }
        return factorial;
    });
    Console.WriteLine($"Thread ID after Task.Run: {Thread.CurrentThread.ManagedThreadId}");
    return result;
}

جدول مقایسه:

ویژگی await Task.Run()
نوع عملیات I/O-bound CPU-bound
مدیریت Thread thread را آزاد می‌کند thread جدید از Thread Pool اشغال می‌کند
هدف اصلی پاسخگویی UI و مقیاس‌پذیری سرور جلوگیری از بلاک شدن thread اصلی توسط محاسبات سنگین
ایجاد Thread جدید خیر بله (از Thread Pool)
پیچیدگی ساده‌سازی کد ناهمزمان اجرای کد همزمان در پس‌زمینه

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

نکته مهم: هرگز Task.Run() را برای عملیات‌های I/O-bound استفاده نکنید، زیرا این کار باعث اشغال بی‌مورد یک thread از Thread Pool می‌شود در حالی که thread می‌تواند آزاد باشد. همیشه برای I/O-bound از متدهای Async موجود استفاده کنید.

سوال 3: Task و Task<T> در .NET چیست؟

پاسخ: Task و Task<T> کلاس‌هایی در .NET هستند که عملیات‌های ناهمزمان را نشان می‌دهند. آن‌ها بخشی از Task Parallel Library (TPL) هستند و سنگ بنای مدل برنامه‌نویسی async/await را تشکیل می‌دهند.

Task:

public async Task PerformActionAsync()
{
    Console.WriteLine("Performing action...");
    await Task.Delay(1000); // شبیه‌سازی عملیات ناهمزمان
    Console.WriteLine("Action completed.");
}

// فراخوانی
public async Task CallAction()
{
    await PerformActionAsync();
    Console.WriteLine("Action method finished.");
}

Task<TResult>:

public async Task<int> GetRandomNumberAsync()
{
    Console.WriteLine("Generating random number...");
    await Task.Delay(500);
    return new Random().Next(1, 100);
}

// فراخوانی
public async Task CallGetNumber()
{
    int number = await GetRandomNumberAsync();
    Console.WriteLine($"Generated number: {number}");
}

ویژگی‌های مشترک:

چرا Task به جای void برای متدهای async؟

نتیجه‌گیری: Task و Task<T> ابزارهای اساسی برای مدیریت عملیات‌های ناهمزمان در .NET هستند. درک صحیح آن‌ها برای نوشتن کد async/await کارآمد و قابل نگهداری ضروری است.

سوال 4: Synchronization Context چیست و چه نقشی در async/await دارد؟

پاسخ: Synchronization Context یک مفهوم کلیدی در .NET است که تعیین می‌کند ادامه یک متد async (کد پس از await) باید در کدام thread اجرا شود. این مفهوم به ویژه در برنامه‌های UI و ASP.NET (قبل از .NET Core) اهمیت دارد.

نقش Synchronization Context:

نحوه کار:

  1. وقتی یک متد async فراخوانی می‌شود، SynchronizationContext.Current در آن لحظه ذخیره می‌شود.
  2. هنگامی که await به یک عملیات ناهمزمان می‌رسد، اگر عملیات هنوز کامل نشده باشد، کنترل به فراخواننده بازگردانده می‌شود و thread آزاد می‌شود.
  3. وقتی عملیات ناهمزمان کامل شد، SynchronizationContext ذخیره شده، ادامه متد را به thread اصلی (UI thread) “پست” (Post) می‌کند تا اجرا شود.

انواع Synchronization Context:

مثال در WPF:

// WPF Window.xaml.cs
public partial class MainWindow : Window
{
    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        // این کد در UI thread اجرا می‌شود
        MyButton.Content = "Downloading...";
        
        // SynchronizationContext.Current در اینجا DispatcherSynchronizationContext است
        string data = await DownloadDataAsync("http://example.com");
        
        // این کد نیز در UI thread اجرا می‌شود (به دلیل Synchronization Context)
        MyButton.Content = "Download Complete";
        MyTextBlock.Text = data;
    }
    
    private async Task<string> DownloadDataAsync(string url)
    {
        using (var client = new HttpClient())
        {
            return await client.GetStringAsync(url);
        }
    }
}

ConfigureAwait(false):

مثال ConfigureAwait(false):

public async Task<string> DownloadDataAndProcessAsync(string url)
{
    // این خط در UI thread (اگر وجود داشته باشد) اجرا می‌شود
    Console.WriteLine($"Before await: {Thread.CurrentThread.ManagedThreadId}");
    
    using (var client = new HttpClient())
    {
        // ادامه کد پس از این await می‌تواند در هر thread از Thread Pool اجرا شود
        string data = await client.GetStringAsync(url).ConfigureAwait(false);
        
        // این خط ممکن است در یک thread متفاوت از Thread Pool اجرا شود
        Console.WriteLine($"After await: {Thread.CurrentThread.ManagedThreadId}");
        
        // پردازش داده (CPU-bound)
        string processedData = data.ToUpper();
        return processedData;
    }
}

// فراخوانی در UI (مثلاً در یک دکمه)
private async void Button_Click(object sender, RoutedEventArgs e)
{
    MyButton.Content = "Processing...";
    string result = await DownloadDataAndProcessAsync("http://example.com");
    // این خط در UI thread اجرا می‌شود (چون ConfigureAwait(false) فقط برای ادامه متد DownloadDataAndProcessAsync اعمال شده بود)
    MyButton.Content = "Done";
    MyTextBlock.Text = result;
}

سوال 5: Deadlock در async/await چیست و چگونه از آن جلوگیری کنیم؟

پاسخ: Deadlock در async/await زمانی رخ می‌دهد که یک متد async در حال انتظار برای تکمیل یک Task باشد، در حالی که Task برای بازگشت به Synchronization Context اصلی (که توسط متد async بلاک شده است) منتظر است. این یک سناریوی رایج در برنامه‌های UI است.

سناریوی Deadlock:

  1. شما یک متد async دارید که در UI thread فراخوانی می‌شود.
  2. این متد به یک عملیات ناهمزمان (مثلاً DownloadDataAsync()) await می‌کند.
  3. قبل از await، Synchronization Context فعلی (UI thread) ذخیره می‌شود.
  4. عملیات ناهمزمان شروع می‌شود و UI thread آزاد می‌شود.
  5. در جایی دیگر از کد، شما به صورت همزمان (با استفاده از .Result یا .Wait()) منتظر تکمیل همان Task می‌شوید.
  6. Task تکمیل می‌شود و سعی می‌کند ادامه کد را به Synchronization Context اصلی (UI thread) بازگرداند.
  7. اما UI thread توسط .Result یا .Wait() بلاک شده است و منتظر تکمیل Task است.
  8. نتیجه: Deadlock.

مثال Deadlock (در یک برنامه UI):

// در UI thread
private void Button_Click(object sender, EventArgs e)
{
    // این خط باعث Deadlock می‌شود!
    string data = DownloadDataAsync().Result; 
    MessageBox.Show(data);
}

private async Task<string> DownloadDataAsync()
{
    await Task.Delay(1000); // شبیه‌سازی عملیات ناهمزمان
    return "Data downloaded";
}

چگونه از Deadlock جلوگیری کنیم؟

  1. همیشه از await استفاده کنید: اگر متد شما async است، همیشه از await برای انتظار برای Task استفاده کنید. هرگز از .Result یا .Wait() در کدهای async استفاده نکنید، مگر اینکه مطمئن باشید در یک thread بدون Synchronization Context هستید (مانند متد Main در برنامه‌های Console).
  2. ConfigureAwait(false): این مهمترین راه برای جلوگیری از Deadlock در کتابخانه‌ها و کدهای غیر UI است. با استفاده از ConfigureAwait(false)، شما به await می‌گویید که نیازی به بازگشت به Synchronization Context اصلی نیست و می‌تواند در هر thread موجود از Thread Pool ادامه یابد. این کار باعث می‌شود که UI thread بلاک نشود و Deadlock رخ ندهد.

مثال رفع Deadlock با ConfigureAwait(false):

// در UI thread
private async void Button_Click(object sender, EventArgs e)
{
    // این خط باعث Deadlock نمی‌شود
    string data = await DownloadDataAsync(); 
    MessageBox.Show(data);
}

private async Task<string> DownloadDataAsync()
{
    await Task.Delay(1000).ConfigureAwait(false); // مهم!
    return "Data downloaded";
}

قانون طلایی:

سوال 6: async void چیست و چرا باید از آن اجتناب کنیم؟

پاسخ: async void یک امضای متد ناهمزمان است که هیچ Task یا Task<T> را برنمی‌گرداند. این نوع متدها فقط برای Event Handlerها (مانند متدهای Button_Click در UI) طراحی شده‌اند و در سایر موارد باید از آن‌ها اجتناب شود.

مشکلات async void:

  1. مدیریت خطا (Exception Handling):
    • استثناهایی که در یک متد async void پرتاب می‌شوند، مستقیماً به Synchronization Context ارسال می‌شوند. اگر Synchronization Context وجود نداشته باشد (مانند برنامه‌های Console یا ASP.NET Core)، استثنا به صورت یک UnhandledException پرتاب می‌شود که می‌تواند برنامه را Crash کند.
    • در مقابل، استثناهای پرتاب شده از async Task در خود Task کپسوله می‌شوند و می‌توانند توسط await کننده با try-catch مدیریت شوند.
  2. عدم قابلیت await کردن:
    • شما نمی‌توانید یک متد async void را await کنید. این بدان معناست که فراخواننده نمی‌تواند بداند چه زمانی عملیات ناهمزمان کامل شده است، که مدیریت جریان کنترل را بسیار دشوار می‌کند.
    • این مشکل به ویژه در تست‌نویسی، جایی که نیاز به انتظار برای تکمیل عملیات ناهمزمان دارید، خود را نشان می‌دهد.
  3. مشکلات جریان کنترل (Control Flow):
    • از آنجایی که نمی‌توانید async void را await کنید، ممکن است کد پس از فراخوانی async void قبل از تکمیل عملیات ناهمزمان اجرا شود، که می‌تواند منجر به رفتارهای غیرمنتظره شود.
  4. تداخل با Synchronization Context:
    • متدهای async void همیشه سعی می‌کنند به Synchronization Context اصلی بازگردند. این می‌تواند در محیط‌هایی که Synchronization Context وجود ندارد (مانند ASP.NET Core) منجر به مشکلات پیش‌بینی نشده شود.

مثال مشکل‌ساز async void:

public class MyService
{
    public async void DoSomethingAndNotify()
    {
        await Task.Delay(1000);
        throw new Exception("An error occurred!"); // این استثنا به صورت UnhandledException پرتاب می‌شود
    }
}

// در متد Main یا یک متد همزمان دیگر
public static void Main(string[] args)
{
    var service = new MyService();
    service.DoSomethingAndNotify();
    Console.WriteLine("This line might execute before the exception is thrown.");
    Console.ReadKey();
}

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

مثال صحیح async void (فقط در Event Handler):

private async void Button_Click(object sender, RoutedEventArgs e)
{
    try
    {
        await DoLongRunningOperationAsync();
        MessageBox.Show("Operation completed!");
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Error: {ex.Message}");
    }
}

نتیجه‌گیری: همیشه سعی کنید از async Task یا async Task<T> استفاده کنید و از async void فقط در Event Handlerها اجتناب‌ناپذیر است، استفاده کنید.

سوال 7: Task.Delay و Thread.Sleep چه تفاوتی دارند؟

پاسخ: هر دو Task.Delay و Thread.Sleep برای ایجاد تأخیر در اجرای کد استفاده می‌شوند، اما تفاوت اساسی در نحوه مدیریت thread‌ها دارند و برای سناریوهای کاملاً متفاوتی به کار می‌روند.

Thread.Sleep(millisecondsTimeout):

مثال Thread.Sleep:

public void DoSomethingSync()
{
    Console.WriteLine($"Start Sync: {DateTime.Now.ToLongTimeString()} - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(2000); // بلاک کردن thread فعلی برای 2 ثانیه
    Console.WriteLine($"End Sync: {DateTime.Now.ToLongTimeString()} - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
}

Task.Delay(millisecondsDelay):

مثال Task.Delay:

public async Task DoSomethingAsync()
{
    Console.WriteLine($"Start Async: {DateTime.Now.ToLongTimeString()} - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
    await Task.Delay(2000); // thread آزاد می‌شود
    Console.WriteLine($"End Async: {DateTime.Now.ToLongTimeString()} - Thread ID: {Thread.CurrentThread.ManagedThreadId}");
}

جدول مقایسه:

ویژگی Thread.Sleep Task.Delay
نوع عملیات همزمان (Synchronous) ناهمزمان (Asynchronous)
بلاک کردن Thread بله، thread فعلی را بلاک می‌کند خیر، thread فعلی را آزاد می‌کند
بازگشت void Task
کاربرد اصلی تست، دیباگینگ (در موارد نادر) تأخیرهای غیر بلاک‌کننده در برنامه‌های ناهمزمان
پاسخگویی UI/سرور کاهش پاسخگویی حفظ پاسخگویی

نتیجه‌گیری: در برنامه‌نویسی مدرن C# و .NET، تقریباً همیشه باید از Task.Delay به جای Thread.Sleep برای ایجاد تأخیر استفاده کنید، مگر اینکه به طور خاص نیاز به بلاک کردن یک thread داشته باشید (که معمولاً توصیه نمی‌شود).

سوال 8: Task.WhenAll و Task.WhenAny چه کاربردی دارند؟

پاسخ: Task.WhenAll و Task.WhenAny متدهای استاتیک در کلاس Task هستند که برای مدیریت چندین عملیات ناهمزمان به صورت همزمان (Concurrency) استفاده می‌شوند. آن‌ها به شما امکان می‌دهند تا برای تکمیل گروهی از Taskها منتظر بمانید.

Task.WhenAll(params Task[] tasks):

مثال Task.WhenAll:

public async Task DownloadMultipleFilesAsync()
{
    var urls = new[] 
    {
        "http://example.com/file1.txt",
        "http://example.com/file2.txt",
        "http://example.com/file3.txt"
    };

    var downloadTasks = urls.Select(async url => 
    {
        using (var client = new HttpClient())
        {
            Console.WriteLine($"Downloading {url}...");
            var content = await client.GetStringAsync(url);
            Console.WriteLine($"Finished {url}");
            return content.Length; // یا هر پردازش دیگری
        }
    }).ToList();

    try
    {
        // منتظر می‌ماند تا همه دانلودها کامل شوند
        var results = await Task.WhenAll(downloadTasks);
        Console.WriteLine($"All downloads completed. Total characters: {results.Sum()}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"An error occurred during downloads: {ex.Message}");
    }
}

Task.WhenAny(params Task[] tasks):

مثال Task.WhenAny:

public async Task GetFirstAvailableDataAsync()
{
    var task1 = Task.Delay(3000).ContinueWith(_ => "Data from Source 1");
    var task2 = Task.Delay(1000).ContinueWith(_ => "Data from Source 2");
    var task3 = Task.Delay(2000).ContinueWith(_ => "Data from Source 3");

    // منتظر می‌ماند تا اولین Task کامل شود
    var completedTask = await Task.WhenAny(task1, task2, task3);

    // بررسی نتیجه Task تکمیل شده
    if (completedTask == task1)
    {
        Console.WriteLine($"First data received from Source 1: {await task1}");
    }
    else if (completedTask == task2)
    {
        Console.WriteLine($"First data received from Source 2: {await task2}");
    }
    else if (completedTask == task3)
    {
        Console.WriteLine($"First data received from Source 3: {await task3}");
    }
}

جدول مقایسه:

ویژگی Task.WhenAll Task.WhenAny
هدف انتظار برای تکمیل تمام Taskها انتظار برای تکمیل اولین Task
بازگشت Task (یا Task<TResult[]>) Task<Task> (یا Task<Task<TResult>>)
مدیریت خطا AggregateException در صورت خطای هر Task خطا را به خودی خود مدیریت نمی‌کند؛ باید Task برگشتی را بررسی کنید
سناریو عملیات‌های مستقل که همه باید کامل شوند رقابت بین عملیات‌ها، پیاده‌سازی timeout

سوال 9: IAsyncEnumerable<T> و yield return async چه کاربردی دارند؟

پاسخ: IAsyncEnumerable<T> و yield return async (که به عنوان Async Streams شناخته می‌شوند) ویژگی‌هایی هستند که در C# 8.0 معرفی شدند و به شما امکان می‌دهند تا داده‌ها را به صورت ناهمزمان و جریانی (Stream) تولید و مصرف کنید. این قابلیت برای سناریوهایی که نیاز به پردازش داده‌ها به صورت تکه تکه و بدون بلاک کردن thread دارید، بسیار مفید است.

مشکل قبل از Async Streams:

IAsyncEnumerable<T>:

yield return async (Async Iterators):

مثال IAsyncEnumerable<T> و yield return async:

public async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
    for (int i = 0; i < count; i++)
    {
        await Task.Delay(100); // شبیه‌سازی عملیات ناهمزمان برای تولید هر عدد
        yield return i;
    }
}

// نحوه مصرف
public async Task ConsumeNumbersAsync()
{
    await foreach (var number in GenerateNumbersAsync(10))
    {
        Console.WriteLine($"Received: {number}");
    }
}

مزایای Async Streams:

کاربردها:

سوال 10: ValueTask و ValueTask<T> چه تفاوتی با Task و Task<T> دارند؟

پاسخ: ValueTask و ValueTask<T> ساختارهایی (structs) هستند که در .NET Core معرفی شدند تا بهینه‌سازی‌هایی را برای سناریوهای خاصی که Task و Task<T> ممکن است سربار (overhead) غیرضروری ایجاد کنند، فراهم کنند. تفاوت اصلی در این است که Task یک کلاس (reference type) است، در حالی که ValueTask یک ساختار (value type) است.

Task و Task<T>:

ValueTask و ValueTask<T>:

جدول مقایسه:

ویژگی Task / Task<T> ValueTask / ValueTask<T>
نوع کلاس (Reference Type) ساختار (Value Type)
تخصیص حافظه (Heap Allocation) همیشه (حتی اگر عملیات همزمان تکمیل شود) فقط زمانی که عملیات ناهمزمان تکمیل شود
عملکرد خوب برای اکثر سناریوها بهینه‌تر برای سناریوهای تکمیل همزمان مکرر
قابلیت‌ها کامل‌تر (پشتیبانی از WhenAll، ContinueWith و غیره) محدودتر (برای قابلیت‌های پیشرفته‌تر نیاز به تبدیل به Task دارد)
کاربرد اصلی عملیات‌های ناهمزمان عمومی عملیات‌های ناهمزمان با احتمال بالای تکمیل همزمان

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

نکته مهم: استفاده نادرست از ValueTask می‌تواند منجر به مشکلات پیچیده‌ای شود، به خصوص اگر چندین بار await شود یا به صورت همزمان await شود. در اکثر موارد، Task و Task<T> انتخاب صحیح هستند.

سوال 11: async و await چگونه در پشت صحنه کار می‌کنند؟ (StateMachine)

پاسخ: زمانی که شما یک متد را با کلمات کلیدی async و await علامت‌گذاری می‌کنید، کامپایلر C# کد شما را به یک StateMachine (ماشین حالت) پیچیده تبدیل می‌کند. این StateMachine مسئول مدیریت جریان کنترل، ذخیره وضعیت متد، و از سرگیری اجرا پس از تکمیل عملیات ناهمزمان است.

مراحل کلی:

  1. تبدیل به StateMachine:
    • کامپایلر متد async شما را به یک کلاس StateMachine تبدیل می‌کند. این کلاس شامل فیلدهایی برای نگهداری متغیرهای محلی و پارامترهای متد است که باید بین نقاط await حفظ شوند.
    • متد async اصلی به یک متد همزمان تبدیل می‌شود که یک نمونه از این StateMachine را ایجاد و شروع می‌کند.
  2. نقاط await:
    • هر عبارت await در متد async به یک نقطه توقف (suspension point) تبدیل می‌شود.
    • در این نقطه، StateMachine وضعیت فعلی متد (مانند مقادیر متغیرهای محلی و خط بعدی برای اجرا) را ذخیره می‌کند.
    • سپس، کنترل به فراخواننده متد async بازگردانده می‌شود و thread آزاد می‌شود.
  3. تکمیل عملیات ناهمزمان:
    • عملیات ناهمزمان (مثلاً دانلود فایل) در پس‌زمینه ادامه می‌یابد.
    • هنگامی که عملیات ناهمزمان کامل می‌شود، نتیجه (یا خطا) به StateMachine اطلاع داده می‌شود.
  4. از سرگیری اجرا:
    • StateMachine وضعیت ذخیره شده را بازیابی می‌کند و اجرای متد async را از نقطه توقف (پس از await) از سر می‌گیرد.
    • این از سرگیری معمولاً در یک thread از Thread Pool اتفاق می‌افتد، مگر اینکه Synchronization Context خاصی وجود داشته باشد (مانند UI thread) که در این صورت StateMachine سعی می‌کند به آن Context بازگردد.

شبه کد (Pseudo-code) از تبدیل:

// کد اصلی async
public async Task<int> GetValueAsync()
{
    Console.WriteLine("Before await");
    int result = await SomeAsyncOperation();
    Console.WriteLine("After await");
    return result;
}

// تبدیل شده توسط کامپایلر به StateMachine
public sealed class <GetValueAsync>d__0 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncTaskMethodBuilder<int> <>t__builder;
    public int <result>5__1;

    private TaskAwaiter<int> <>u__1;

    void IAsyncStateMachine.MoveNext()
    {
        int num = <>1__state;
        try
        {
            TaskAwaiter<int> awaiter;
            if (num != 0)
            {
                Console.WriteLine("Before await");
                awaiter = SomeAsyncOperation().GetAwaiter();
                if (!awaiter.IsCompleted)
                {
                    <>1__state = 0;
                    <>u__1 = awaiter;
                    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                    return;
                }
            }
            else
            {
                awaiter = <>u__1;
                <>u__1 = default(TaskAwaiter<int>);
                <>1__state = -1;
            }
            <result>5__1 = awaiter.GetResult();
            Console.WriteLine("After await");
        }
        catch (Exception exception)
        {
            <>1__state = -2;
            <>t__builder.SetException(exception);
            return;
        }
        <>1__state = -2;
        <>t__builder.SetResult(<result>5__1);
    }

    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
    {
        <>t__builder.SetStateMachine(stateMachine);
    }
}

مزایای StateMachine:

نتیجه‌گیری: StateMachine یک الگوی قدرتمند است که در پشت صحنه async/await کار می‌کند و پیچیدگی‌های برنامه‌نویسی ناهمزمان را از دید توسعه‌دهنده پنهان می‌کند. درک این مکانیسم به شما کمک می‌کند تا کدهای async/await بهتری بنویسید و مشکلات احتمالی (مانند Deadlock) را تشخیص دهید.

سوال 12: async و await در ASP.NET Core چگونه کار می‌کنند؟

پاسخ: استفاده از async و await در ASP.NET Core برای بهبود مقیاس‌پذیری و پاسخگویی برنامه‌های وب بسیار حیاتی است. برخلاف نسخه‌های قدیمی‌تر ASP.NET (Full Framework)، ASP.NET Core به طور پیش‌فرض از Synchronization Context استفاده نمی‌کند، که این امر مدیریت async/await را ساده‌تر و کارآمدتر می‌کند.

نحوه کار در ASP.NET Core:

  1. درخواست ورودی:
    • هنگامی که یک درخواست HTTP به ASP.NET Core می‌رسد، یک thread از Thread Pool برای پردازش اولیه درخواست اختصاص داده می‌شود.
  2. عملیات ناهمزمان (I/O-bound):
    • اگر کنترلر یا سرویس شما یک عملیات I/O-bound (مانند فراخوانی دیتابیس، API خارجی، یا خواندن/نوشتن فایل) را با await فراخوانی کند:
    • thread فعلی از Thread Pool آزاد می‌شود و به Pool بازگردانده می‌شود تا درخواست‌های دیگر را پردازش کند.
    • عملیات I/O توسط سیستم عامل یا سخت‌افزار به صورت ناهمزمان انجام می‌شود.
  3. تکمیل عملیات ناهمزمان:
    • هنگامی که عملیات I/O کامل می‌شود، یک Callback به Thread Pool ارسال می‌شود.
    • یک thread دیگر (یا همان thread قبلی اگر آزاد باشد) از Thread Pool، Callback را دریافت کرده و ادامه اجرای متد async را از سر می‌گیرد.
  4. عدم وجود Synchronization Context:
    • از آنجایی که ASP.NET Core به طور پیش‌فرض Synchronization Context ندارد، نیازی به بازگشت به یک thread خاص پس از await نیست. این بدان معناست که شما معمولاً نیازی به استفاده از .ConfigureAwait(false) در کدهای ASP.NET Core ندارید، مگر اینکه در حال فراخوانی کد کتابخانه‌ای باشید که ممکن است Deadlock ایجاد کند.

مزایای استفاده از async/await در ASP.NET Core:

مثال در ASP.NET Core Controller:

public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
    {
        // thread آزاد می‌شود در حین انتظار برای دیتابیس
        var products = await _productService.GetAllProductsAsync(); 
        return Ok(products);
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var product = await _productService.GetProductByIdAsync(id);
        if (product == null)
        {
            return NotFound();
        }
        return Ok(product);
    }
}

نکات مهم:

سوال 13: async و await در Entity Framework Core چگونه کار می‌کنند؟

پاسخ: async و await در Entity Framework Core (EF Core) برای اجرای عملیات‌های دیتابیس به صورت ناهمزمان استفاده می‌شوند. این قابلیت به طور قابل توجهی مقیاس‌پذیری و پاسخگویی برنامه‌هایی را که با دیتابیس تعامل دارند، بهبود می‌بخشد، به خصوص در برنامه‌های وب و سرویس‌ها.

چرا از async/await در EF Core استفاده کنیم؟

متدهای async رایج در EF Core:

مثال استفاده از async/await در EF Core:

public class ProductRepository : IProductRepository
{
    private readonly ApplicationDbContext _context;

    public ProductRepository(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<IEnumerable<Product>> GetAllProductsAsync()
    {
        // thread آزاد می‌شود در حین انتظار برای دیتابیس
        return await _context.Products.ToListAsync(); 
    }

    public async Task<Product> GetProductByIdAsync(int id)
    {
        return await _context.Products.FirstOrDefaultAsync(p => p.Id == id);
    }

    public async Task AddProductAsync(Product product)
    {
        _context.Products.Add(product);
        await _context.SaveChangesAsync(); // thread آزاد می‌شود در حین انتظار برای دیتابیس
    }

    public async Task UpdateProductAsync(Product product)
    {
        _context.Entry(product).State = EntityState.Modified;
        await _context.SaveChangesAsync();
    }

    public async Task DeleteProductAsync(int id)
    {
        var product = await _context.Products.FindAsync(id);
        if (product != null)
        {
            _context.Products.Remove(product);
            await _context.SaveChangesAsync();
        }
    }
}

نکات مهم:

سوال 14: Cancellation Token چیست و چگونه در async/await استفاده می‌شود؟

پاسخ: Cancellation Token یک مکانیسم استاندارد در .NET برای هماهنگی لغو عملیات‌های ناهمزمان است. این به شما امکان می‌دهد تا به یک عملیات در حال اجرا سیگنال دهید که باید متوقف شود، بدون اینکه به طور ناگهانی آن را قطع کنید.

چرا به Cancellation Token نیاز داریم؟

نحوه استفاده:

  1. CancellationTokenSource: این کلاس برای ایجاد و مدیریت CancellationToken استفاده می‌شود. شما یک نمونه از آن را ایجاد می‌کنید و سپس Token آن را به متدهای ناهمزمان خود پاس می‌دهید.
  2. CancellationToken: این یک ساختار (struct) است که به متدهای ناهمزمان پاس داده می‌شود. متدها می‌توانند وضعیت این Token را بررسی کنند تا ببینند آیا درخواست لغو ارسال شده است یا خیر.
  3. ThrowIfCancellationRequested(): این متد روی CancellationToken فراخوانی می‌شود و اگر درخواست لغو ارسال شده باشد، یک OperationCanceledException پرتاب می‌کند.
  4. Register(): برای ثبت یک Callback که هنگام لغو Token اجرا می‌شود.
  5. Cancel(): این متد روی CancellationTokenSource فراخوانی می‌شود تا درخواست لغو را به تمام Tokenهای مرتبط ارسال کند.

مثال استفاده از CancellationToken:

public async Task DoLongRunningOperationAsync(CancellationToken cancellationToken)
{
    for (int i = 0; i < 10; i++)
    {
        // هر بار قبل از انجام کار، وضعیت لغو را بررسی می‌کنیم
        cancellationToken.ThrowIfCancellationRequested();

        Console.WriteLine($"Processing step {i + 1}...");
        await Task.Delay(500, cancellationToken); // Task.Delay هم CancellationToken می‌گیرد
    }
    Console.WriteLine("Operation completed.");
}

public async Task RunOperationWithCancellation()
{
    using (var cts = new CancellationTokenSource())
    {
        var operationTask = DoLongRunningOperationAsync(cts.Token);

        // بعد از 2 ثانیه، درخواست لغو می‌دهیم
        await Task.Delay(2000);
        cts.Cancel();

        try
        {
            await operationTask;
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Operation was cancelled!");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An error occurred: {ex.Message}");
        }
    }
}

نکات مهم:

سوال 15: async و await در ASP.NET Web Forms چگونه کار می‌کنند؟

پاسخ: استفاده از async و await در ASP.NET Web Forms (که یک فریم‌ورک قدیمی‌تر است) کمی پیچیده‌تر از ASP.NET Core است، زیرا Web Forms به شدت به Synchronization Context متکی است. با این حال، استفاده از async/await می‌تواند به بهبود مقیاس‌پذیری برنامه‌های Web Forms کمک کند، به خصوص برای عملیات‌های I/O-bound.

نحوه کار در ASP.NET Web Forms:

  1. Synchronization Context:
    • ASP.NET Web Forms دارای یک AspNetSynchronizationContext است که تضمین می‌کند ادامه کد پس از await به thread اصلی درخواست (Request Thread) بازگردانده شود. این برای حفظ HttpContext و سایر اطلاعات مربوط به درخواست ضروری است.
  2. ثبت متدهای ناهمزمان:
    • برای اینکه ASP.NET Web Forms از وجود عملیات ناهمزمان آگاه شود، باید متدهای async را در چرخه حیات صفحه (Page Lifecycle) ثبت کنید. این کار معمولاً با استفاده از Page.RegisterAsyncTask یا با علامت‌گذاری متد Page_Load یا Button_Click به عنوان async void انجام می‌شود.
  3. آزاد کردن Thread:
    • هنگامی که یک عملیات I/O-bound با await فراخوانی می‌شود، thread فعلی درخواست آزاد می‌شود و به Thread Pool بازگردانده می‌شود.
    • این thread می‌تواند درخواست‌های دیگر را پردازش کند، که منجر به افزایش Throughput سرور می‌شود.
  4. بازگشت به Context:
    • پس از تکمیل عملیات ناهمزمان، AspNetSynchronizationContext تضمین می‌کند که ادامه کد به همان thread اصلی درخواست (یا یک thread دیگر از Thread Pool که به آن Context متصل شده است) بازگردانده شود.

مثال در ASP.NET Web Forms:

// Default.aspx.cs
public partial class _Default : Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // ثبت یک عملیات ناهمزمان در چرخه حیات صفحه
        RegisterAsyncTask(new PageAsyncTask(LoadDataAsync));
    }

    private async Task LoadDataAsync()
    {
        // شبیه‌سازی عملیات طولانی دیتابیس یا API
        string data = await GetDataFromServiceAsync();
        lblData.Text = data;
    }

    private async Task<string> GetDataFromServiceAsync()
    {
        using (var client = new HttpClient())
        {
            return await client.GetStringAsync("http://example.com/api/data");
        }
    }

    protected async void btnSubmit_Click(object sender, EventArgs e)
    {
        // این متد async void است و برای Event Handlerها مناسب است
        string result = await ProcessFormAsync();
        lblResult.Text = result;
    }

    private async Task<string> ProcessFormAsync()
    {
        await Task.Delay(1500); // شبیه‌سازی پردازش فرم
        return "Form processed successfully!";
    }
}

نکات مهم و مشکلات احتمالی:

سوال 16: async و await در WPF و WinForms چگونه کار می‌کنند؟

پاسخ: در برنامه‌های دسکتاپ WPF و WinForms، async و await برای حفظ پاسخگویی رابط کاربری (UI Responsiveness) در حین انجام عملیات‌های طولانی مدت استفاده می‌شوند. این فریم‌ورک‌ها به شدت به یک UI thread واحد متکی هستند که مسئول به‌روزرسانی UI و پردازش ورودی‌های کاربر است.

نحوه کار در WPF/WinForms:

  1. UI Thread و Synchronization Context:
    • هر دو WPF و WinForms دارای یک UI thread اصلی هستند. تمام عملیات‌های مربوط به UI (مانند به‌روزرسانی کنترل‌ها، دسترسی به ویژگی‌های UI) باید در این thread انجام شوند.
    • WPF از DispatcherSynchronizationContext و WinForms از WindowsFormsSynchronizationContext استفاده می‌کنند. این Contextها تضمین می‌کنند که ادامه کد پس از await به UI thread بازگردانده شود.
  2. عملیات ناهمزمان:
    • هنگامی که یک متد async در UI thread فراخوانی می‌شود و به یک عملیات ناهمزمان (مانند فراخوانی API، دسترسی به دیتابیس، یا Task.Delay) await می‌کند:
    • UI thread آزاد می‌شود و می‌تواند به پردازش رویدادهای UI و حفظ پاسخگویی ادامه دهد.
    • عملیات ناهمزمان در یک thread دیگر (معمولاً از Thread Pool) یا توسط سیستم عامل انجام می‌شود.
  3. بازگشت به UI Thread:
    • پس از تکمیل عملیات ناهمزمان، Synchronization Context (Dispatcher یا Windows Forms Synchronization Context) تضمین می‌کند که ادامه کد پس از await به UI thread “پست” (Post) شود. این کار به شما امکان می‌دهد تا بدون نگرانی از خطاهای cross-thread، UI را به‌روزرسانی کنید.

مثال در WPF:

// MainWindow.xaml.cs
public partial class MainWindow : Window
{
    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        // این کد در UI thread اجرا می‌شود
        MyButton.Content = "Downloading...";
        
        // UI thread آزاد می‌شود
        string data = await DownloadDataAsync("http://example.com");
        
        // این کد به UI thread بازمی‌گردد و UI را به‌روزرسانی می‌کند
        MyButton.Content = "Download Complete";
        MyTextBlock.Text = data;
    }
    
    private async Task<string> DownloadDataAsync(string url)
    {
        using (var client = new HttpClient())
        {
            return await client.GetStringAsync(url);
        }
    }
}

نکات مهم:

سوال 17: async و await در Console Application چگونه کار می‌کنند؟

پاسخ: استفاده از async و await در Console Applicationها برای اجرای عملیات‌های ناهمزمان (مانند فراخوانی API، دسترسی به فایل) بدون بلاک کردن thread اصلی برنامه استفاده می‌شود. تفاوت اصلی در Console Applicationها این است که به طور پیش‌فرض Synchronization Context خاصی وجود ندارد.

نحوه کار در Console Application:

  1. عدم وجود Synchronization Context:
    • در یک Console Application، SynchronizationContext.Current معمولاً null است. این بدان معناست که پس از await، ادامه کد در هر thread موجود از Thread Pool اجرا می‌شود.
    • این ویژگی باعث می‌شود که Deadlockهای مربوط به Synchronization Context در Console Applicationها رخ ندهد.
  2. متد Main ناهمزمان:
    • از C# 7.1 به بعد، می‌توانید متد Main را به صورت async Task Main() یا async Task<int> Main() تعریف کنید. این کار به شما امکان می‌دهد تا await را مستقیماً در متد Main استفاده کنید.
  3. عملیات ناهمزمان:
    • هنگامی که یک عملیات I/O-bound با await فراخوانی می‌شود، thread اصلی برنامه آزاد می‌شود و می‌تواند کارهای دیگری را انجام دهد (اگرچه در یک برنامه Console ساده، معمولاً کار دیگری برای انجام وجود ندارد).

مثال در Console Application:

public class Program
{
    public static async Task Main(string[] args)
    {
        Console.WriteLine($"Main thread ID: {Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine("Starting long running operation...");

        string result = await GetLongRunningResultAsync();

        Console.WriteLine($"Operation completed. Result: {result}");
        Console.WriteLine($"Current thread ID: {Thread.CurrentThread.ManagedThreadId}");
        Console.ReadKey(); // برای نگه داشتن کنسول باز
    }

    public static async Task<string> GetLongRunningResultAsync()
    {
        Console.WriteLine($"Inside GetLongRunningResultAsync - Thread ID before delay: {Thread.CurrentThread.ManagedThreadId}");
        await Task.Delay(3000); // شبیه‌سازی عملیات طولانی
        Console.WriteLine($"Inside GetLongRunningResultAsync - Thread ID after delay: {Thread.CurrentThread.ManagedThreadId}");
        return "Data from async operation";
    }
}

نکات مهم:

سوال 18: async و await در Unit Testing چگونه کار می‌کنند؟

پاسخ: تست کردن کدهای ناهمزمان با async و await نیازمند رویکرد صحیح است تا اطمینان حاصل شود که تست‌ها به درستی اجرا می‌شوند و نتایج قابل اعتمادی ارائه می‌دهند. فریم‌ورک‌های تست مدرن (مانند xUnit, NUnit, MSTest) از متدهای تست async پشتیبانی می‌کنند.

نحوه تست کدهای async:

  1. علامت‌گذاری متد تست به عنوان async Task:
    • به جای void، متد تست خود را به عنوان async Task علامت‌گذاری کنید. این به فریم‌ورک تست می‌گوید که این یک تست ناهمزمان است و باید منتظر تکمیل Task برگشتی باشد.
  2. await کردن عملیات ناهمزمان:
    • در داخل متد تست، عملیات ناهمزمان مورد نظر را با await فراخوانی کنید. این تضمین می‌کند که تست تا زمانی که عملیات ناهمزمان کامل شود، منتظر می‌ماند.
  3. مدیریت خطا:
    • استثناهایی که از متدهای async Task پرتاب می‌شوند، به درستی توسط فریم‌ورک تست مدیریت می‌شوند. می‌توانید از Assert.ThrowsAsync<T> (در xUnit) یا مشابه آن برای تست پرتاب استثناهای ناهمزمان استفاده کنید.

مثال تست ناهمزمان با xUnit:

public class MyServiceTests
{
    [Fact]
    public async Task GetDataAsync_ReturnsExpectedData()
    {
        // Arrange
        var service = new MyService();

        // Act
        string result = await service.GetDataAsync();

        // Assert
        Assert.Equal("Some Data", result);
    }

    [Fact]
    public async Task GetDataAsync_ThrowsExceptionOnFailure()
    {
        // Arrange
        var service = new MyService(shouldThrow: true);

        // Act & Assert
        await Assert.ThrowsAsync<InvalidOperationException>(() => service.GetDataAsync());
    }
}

public class MyService
{
    private readonly bool _shouldThrow;

    public MyService(bool shouldThrow = false)
    {
        _shouldThrow = shouldThrow;
    }

    public async Task<string> GetDataAsync()
    {
        await Task.Delay(100); // شبیه‌سازی عملیات ناهمزمان
        if (_shouldThrow)
        {
            throw new InvalidOperationException("Failed to get data.");
        }
        return "Some Data";
    }
}

نکات مهم:

سوال 19: SynchronizationContext.SetSynchronizationContext چه کاربردی دارد؟

پاسخ: متد استاتیک SynchronizationContext.SetSynchronizationContext(SynchronizationContext syncContext) برای تنظیم SynchronizationContext فعلی برای thread جاری استفاده می‌شود. این متد به ندرت در کدهای برنامه‌های کاربردی معمولی استفاده می‌شود و عمدتاً توسط فریم‌ورک‌ها (مانند WPF, WinForms, ASP.NET) در پشت صحنه برای تنظیم Context مناسب استفاده می‌شود.

کاربردها (عمدتاً داخلی فریم‌ورک):

  1. تنظیم Context در UI Frameworks:
    • هنگامی که یک برنامه WPF یا WinForms شروع به کار می‌کند، فریم‌ورک به طور خودکار DispatcherSynchronizationContext یا WindowsFormsSynchronizationContext را برای UI thread تنظیم می‌کند. این کار تضمین می‌کند که هر await در UI thread، ادامه کد را به همان UI thread بازگرداند.
  2. تنظیم Context در ASP.NET (Full Framework):
    • در ASP.NET (Full Framework)، AspNetSynchronizationContext برای هر درخواست تنظیم می‌شود تا اطمینان حاصل شود که HttpContext و سایر اطلاعات درخواست در دسترس باقی می‌مانند.
  3. سناریوهای تست (نادر):
    • در برخی سناریوهای تست پیشرفته، ممکن است نیاز باشد که یک SynchronizationContext سفارشی برای شبیه‌سازی رفتار یک محیط خاص (مانند UI) تنظیم شود. این کار به تست کدهای ناهمزمان کمک می‌کند که به Synchronization Context وابسته هستند.
  4. پیاده‌سازی فریم‌ورک‌های سفارشی:
    • اگر در حال ساخت یک فریم‌ورک یا کتابخانه هستید که نیاز به مدیریت دقیق thread‌ها و Contextها دارد، ممکن است از این متد استفاده کنید.

مثال (فقط برای درک، استفاده در کد واقعی توصیه نمی‌شود):

public class CustomSynchronizationContext : SynchronizationContext
{
    public override void Post(SendOrPostCallback d, object state)
    {
        // در اینجا می‌توانید منطق سفارشی برای اجرای Callback را پیاده‌سازی کنید
        Console.WriteLine($"Custom Context: Posting callback on thread {Thread.CurrentThread.ManagedThreadId}");
        ThreadPool.QueueUserWorkItem(_ => d(state));
    }

    public override void Send(SendOrPostCallback d, object state)
    {
        // در اینجا می‌توانید منطق سفارشی برای اجرای Callback را پیاده‌سازی کنید
        Console.WriteLine($"Custom Context: Sending callback on thread {Thread.CurrentThread.ManagedThreadId}");
        d(state);
    }
}

public class Program
{
    public static async Task Main(string[] args)
    {
        // تنظیم یک SynchronizationContext سفارشی برای thread اصلی
        SynchronizationContext.SetSynchronizationContext(new CustomSynchronizationContext());

        Console.WriteLine($"Main thread ID before async call: {Thread.CurrentThread.ManagedThreadId}");
        await DoSomethingAsync();
        Console.WriteLine($"Main thread ID after async call: {Thread.CurrentThread.ManagedThreadId}");

        Console.ReadKey();
    }

    public static async Task DoSomethingAsync()
    {
        Console.WriteLine($"Inside DoSomethingAsync - Thread ID before await: {Thread.CurrentThread.ManagedThreadId}");
        await Task.Delay(1000);
        Console.WriteLine($"Inside DoSomethingAsync - Thread ID after await: {Thread.CurrentThread.ManagedThreadId}");
    }
}

نکات مهم:

سوال 20: ExecutionContext و LogicalCallContext چه نقشی در async/await دارند؟

پاسخ: ExecutionContext و LogicalCallContext (که بخشی از ExecutionContext است) مفاهیم پیشرفته‌تری در .NET هستند که به مدیریت و انتقال اطلاعات مربوط به Context در طول عملیات‌های ناهمزمان کمک می‌کنند. آن‌ها تضمین می‌کنند که اطلاعات مهمی مانند هویت کاربر، تنظیمات محلی (Culture)، و تراکنش‌ها در طول فراخوانی‌های ناهمزمان حفظ شوند.

ExecutionContext:

LogicalCallContext:

مثال استفاده از LogicalCallContext (از طریق AsyncLocal<T>):

// در .NET Core و نسخه‌های جدیدتر، AsyncLocal<T> جایگزین LogicalCallContext شده است
public class Program
{
    private static readonly AsyncLocal<string> _correlationId = new AsyncLocal<string>();

    public static async Task Main(string[] args)
    {
        _correlationId.Value = Guid.NewGuid().ToString();
        Console.WriteLine($"Main thread: Correlation ID = {_correlationId.Value}");

        await ProcessRequestAsync();

        Console.WriteLine($"Main thread after async call: Correlation ID = {_correlationId.Value}");
        Console.ReadKey();
    }

    public static async Task ProcessRequestAsync()
    {
        Console.WriteLine($"ProcessRequestAsync: Correlation ID = {_correlationId.Value}");
        await Task.Delay(1000);
        Console.WriteLine($"ProcessRequestAsync after delay: Correlation ID = {_correlationId.Value}");
    }
}

نکات مهم:

سوال 21: async و await در LINQ چگونه کار می‌کنند؟

پاسخ: استفاده از async و await در LINQ به طور مستقیم توسط LINQ to Objects پشتیبانی نمی‌شود. با این حال، می‌توانید از async/await در داخل عبارات LINQ استفاده کنید، به خصوص با استفاده از Task.WhenAll برای اجرای همزمان عملیات‌های ناهمزمان.

سناریوهای رایج:

  1. اجرای همزمان عملیات ناهمزمان روی یک مجموعه:
    • شما می‌توانید از Select برای ایجاد یک مجموعه از Taskها استفاده کنید و سپس با Task.WhenAll منتظر تکمیل همه آن‌ها بمانید.
  2. فیلتر کردن ناهمزمان:
    • شما می‌توانید یک متد ناهمزمان برای فیلتر کردن عناصر ایجاد کنید و سپس از آن در داخل Where استفاده کنید.
  3. استفاده از IAsyncEnumerable<T>:
    • با معرفی IAsyncEnumerable<T> در C# 8.0، می‌توانید از LINQ به صورت ناهمزمان با استفاده از await foreach و متدهای LINQ ناهمزمان (مانند WhereAwait, SelectAwait) استفاده کنید. این قابلیت در کتابخانه‌هایی مانند System.Linq.Async پیاده‌سازی شده است.

مثال اجرای همزمان با Task.WhenAll:

public async Task ProcessUrlsAsync(IEnumerable<string> urls)
{
    var downloadTasks = urls.Select(async url =>
    {
        using (var client = new HttpClient())
        {
            return await client.GetStringAsync(url);
        }
    });

    var results = await Task.WhenAll(downloadTasks);

    foreach (var result in results)
    {
        Console.WriteLine(result.Substring(0, 50));
    }
}

مثال فیلتر کردن ناهمزمان:

public async Task<IEnumerable<string>> FilterValidUrlsAsync(IEnumerable<string> urls)
{
    var validUrls = new List<string>();
    foreach (var url in urls)
    {
        if (await IsUrlValidAsync(url))
        {
            validUrls.Add(url);
        }
    }
    return validUrls;
}

private async Task<bool> IsUrlValidAsync(string url)
{
    try
    {
        using (var client = new HttpClient())
        {
            var response = await client.GetAsync(url);
            return response.IsSuccessStatusCode;
        }
    }
    catch
    {
        return false;
    }
}

نکات مهم:

سوال 22: async و await در Constructorها چگونه کار می‌کنند؟

پاسخ: شما نمی‌توانید یک Constructor را به عنوان async علامت‌گذاری کنید. این یک محدودیت در C# است، زیرا Constructorها باید به صورت همزمان اجرا شوند و یک شیء را برگردانند.

چرا نمی‌توان Constructor را async کرد؟

راه حل‌ها:

  1. Factory Method Pattern: این بهترین و رایج‌ترین راه حل است. شما یک متد استاتیک async ایجاد می‌کنید که مسئول ایجاد و مقداردهی اولیه ناهمزمان شیء است.
  2. متد InitializeAsync: شما یک متد async جداگانه برای مقداردهی اولیه ایجاد می‌کنید که باید پس از ایجاد شیء فراخوانی شود. این رویکرد کمتر امن است، زیرا ممکن است فراموش کنید متد InitializeAsync را فراخوانی کنید.

مثال Factory Method Pattern:

public class MyService
{
    private MyService() { }

    public static async Task<MyService> CreateAsync()
    {
        var service = new MyService();
        await service.InitializeAsync();
        return service;
    }

    private async Task InitializeAsync()
    {
        // عملیات مقداردهی اولیه ناهمزمان
        await Task.Delay(1000);
    }
}

// نحوه استفاده
public async Task UseService()
{
    var service = await MyService.CreateAsync();
    // ...
}

مثال متد InitializeAsync:

public class MyService
{
    public async Task InitializeAsync()
    {
        // عملیات مقداردهی اولیه ناهمزمان
        await Task.Delay(1000);
    }
}

// نحوه استفاده
public async Task UseService()
{
    var service = new MyService();
    await service.InitializeAsync();
    // ...
}

سوال 23: async و await در Property Getters/Setters چگونه کار می‌کنند؟

پاسخ: شما نمی‌توانید Property Getters/Setters را به عنوان async علامت‌گذاری کنید. این یک محدودیت دیگر در C# است.

چرا نمی‌توان Property را async کرد؟

راه حل‌ها:

  1. متدهای Get/Set ناهمزمان: به جای Property، متدهای async جداگانه برای دریافت و تنظیم مقدار ایجاد کنید.
  2. Lazy Initialization: اگر نیاز به مقداردهی اولیه ناهمزمان دارید، می‌توانید از الگوی Lazy Initialization با استفاده از AsyncLazy<T> استفاده کنید.

مثال متدهای Get/Set ناهمزمان:

public class MyClass
{
    private string _myValue;

    public async Task<string> GetMyValueAsync()
    {
        if (_myValue == null)
        {
            _myValue = await LoadValueFromDatabaseAsync();
        }
        return _myValue;
    }

    public async Task SetMyValueAsync(string value)
    {
        await SaveValueToDatabaseAsync(value);
        _myValue = value;
    }

    private async Task<string> LoadValueFromDatabaseAsync()
    {
        await Task.Delay(500);
        return "Some Value";
    }

    private async Task SaveValueToDatabaseAsync(string value)
    {
        await Task.Delay(500);
    }
}

سوال 24: async و await در Locking چگونه کار می‌کنند؟

پاسخ: شما نمی‌توانید از await در داخل یک بلوک lock استفاده کنید. این یک محدودیت در C# است، زیرا lock یک مکانیسم همزمان است که thread را بلاک می‌کند.

چرا نمی‌توان await را در lock استفاده کرد؟

راه حل‌ها:

  1. SemaphoreSlim: این بهترین و رایج‌ترین راه حل برای Locking ناهمزمان است. SemaphoreSlim یک مکانیسم Locking ناهمزمان است که به شما امکان می‌دهد با استفاده از WaitAsync() و Release()، دسترسی به یک منبع را به صورت ناهمزمان مدیریت کنید.

مثال استفاده از SemaphoreSlim:

public class MySharedResource
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

    public async Task AccessResourceAsync()
    {
        await _semaphore.WaitAsync();
        try
        {
            // عملیات ناهمزمان روی منبع مشترک
            await Task.Delay(1000);
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

سوال 25: async و await در Exception Handling چگونه کار می‌کنند؟

پاسخ: مدیریت خطا در کدهای async/await بسیار شبیه به کدهای همزمان است، اما با چند تفاوت کلیدی.

نحوه کار:

مثال مدیریت خطا:

public async Task RunAsync()
{
    try
    {
        await DoSomethingAsync();
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Caught exception: {ex.Message}");
    }
}

public async Task DoSomethingAsync()
{
    await Task.Delay(100);
    throw new InvalidOperationException("Something went wrong!");
}

نکات مهم:

سوال 26: async و await در Performance چه تأثیری دارند؟

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

تأثیرات عملکردی:

نکات بهینه‌سازی:

سوال 27: async و await در .NET Standard چگونه کار می‌کنند؟

پاسخ: async/await در .NET Standard به طور کامل پشتیبانی می‌شود. این به شما امکان می‌دهد تا کتابخانه‌های ناهمزمان بنویسید که می‌توانند در پلتفرم‌های مختلف .NET (مانند .NET Core, .NET Framework, Xamarin) استفاده شوند.

نکات مهم:

سوال 28: async و await در Blazor چگونه کار می‌کنند؟

پاسخ: در Blazor، async/await برای اجرای عملیات‌های ناهمزمان (مانند فراخوانی API) بدون بلاک کردن UI thread استفاده می‌شود.

نحوه کار:

مثال در Blazor:

@page "/fetchdata"
@inject HttpClient Http

<h1>Weather forecast</h1>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    // ...
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
    }
}

سوال 29: async و await در MAUI و Xamarin چگونه کار می‌کنند؟

پاسخ: در MAUI و Xamarin، async/await برای حفظ پاسخگویی UI در برنامه‌های موبایل استفاده می‌شود. این فریم‌ورک‌ها نیز دارای UI thread و Synchronization Context هستند.

نحوه کار:

مثال در MAUI:

private async void OnCounterClicked(object sender, EventArgs e)
{
    // این کد در UI thread اجرا می‌شود
    CounterBtn.Text = "Loading...";

    // UI thread آزاد می‌شود
    string data = await DownloadDataAsync();

    // این کد به UI thread بازمی‌گردد
    CounterBtn.Text = data;
}

سوال 30: async و await در .NET 8 چه تغییراتی داشته‌اند؟

پاسخ: در .NET 8، بهبودهای عملکردی و بهینه‌سازی‌هایی در async/await و Taskها انجام شده است. برخی از این تغییرات عبارتند از:

ذخیره می‌شود. پس از تکمیل عملیات ناهمزمان، ExecutionContext ذخیره شده بازیابی می‌شود و ادامه کد در آن Context اجرا می‌شود. این تضمین می‌کند که اطلاعات مهم Context در طول عملیات ناهمزمان از بین نرود.

LogicalCallContext:

مثال استفاده از LogicalCallContext (از طریق AsyncLocal<T>):

// در .NET Core و نسخه‌های جدیدتر، AsyncLocal<T> جایگزین LogicalCallContext شده است
public class Program
{
    private static readonly AsyncLocal<string> _correlationId = new AsyncLocal<string>();

    public static async Task Main(string[] args)
    {
        _correlationId.Value = Guid.NewGuid().ToString();
        Console.WriteLine($"Main thread: Correlation ID = {_correlationId.Value}");

        await ProcessRequestAsync();

        Console.WriteLine($"Main thread after async call: Correlation ID = {_correlationId.Value}");
        Console.ReadKey();
    }

    public static async Task ProcessRequestAsync()
    {
        Console.WriteLine($"ProcessRequestAsync: Correlation ID = {_correlationId.Value}");
        await Task.Delay(1000);
        Console.WriteLine($"ProcessRequestAsync after delay: Correlation ID = {_correlationId.Value}");
    }
}

نکات مهم:

سوال 31: async و await در Azure Functions چگونه کار می‌کنند؟

پاسخ: async/await در Azure Functions به طور کامل پشتیبانی می‌شود و استفاده از آن برای بهبود مقیاس‌پذیری و پاسخگویی توابع بسیار مهم است. Azure Functions یک سرویس Serverless است که به شما امکان می‌دهد کد را بدون نیاز به مدیریت زیرساخت اجرا کنید.

نحوه کار در Azure Functions:

  1. مقیاس‌پذیری:
    • هنگامی که یک تابع async در Azure Functions به یک عملیات I/O-bound (مانند فراخوانی دیتابیس، API خارجی، یا صف پیام) await می‌کند، thread اجرایی آزاد می‌شود و به Thread Pool بازگردانده می‌شود.
    • این thread می‌تواند برای پردازش درخواست‌های دیگر یا اجرای توابع دیگر استفاده شود. این کار به Azure Functions اجازه می‌دهد تا تعداد بیشتری درخواست همزمان را با منابع کمتر مدیریت کند، که منجر به کاهش هزینه‌ها و بهبود عملکرد می‌شود.
  2. پاسخگویی:
    • برای توابع HTTP Trigger، استفاده از async/await تضمین می‌کند که تابع در حین انتظار برای عملیات‌های خارجی، بلاک نمی‌شود و می‌تواند به سرعت به درخواست‌های جدید پاسخ دهد.
  3. مدیریت Context:
    • Azure Functions به طور خودکار Contextهای لازم را در طول عملیات‌های ناهمزمان مدیریت می‌کند، بنابراین شما نیازی به نگرانی در مورد Synchronization Context ندارید.

مثال در Azure Functions (C#):

public static class MyHttpTrigger
{
    [FunctionName("MyHttpTrigger")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");

        string name = req.Query["name"];

        // شبیه‌سازی عملیات طولانی مدت I/O-bound
        string responseMessage = await CallExternalApiAsync(name);

        return new OkObjectResult(responseMessage);
    }

    private static async Task<string> CallExternalApiAsync(string name)
    {
        // در اینجا می‌توانید یک فراخوانی واقعی به یک API خارجی یا دیتابیس داشته باشید
        await Task.Delay(2000); // شبیه‌سازی تاخیر شبکه
        return $"Hello, {name ?? "World"}. This HTTP triggered function executed successfully asynchronously.";
    }
}

نکات مهم:

سوال 32: async و await در Event-Driven Architectures چگونه کار می‌کنند؟

پاسخ: async/await نقش حیاتی در Event-Driven Architectures (معماری‌های رویداد محور) ایفا می‌کند، به خصوص در سناریوهایی که نیاز به پردازش رویدادها به صورت ناهمزمان و بدون بلاک کردن thread اصلی یا مصرف بیش از حد منابع وجود دارد.

نحوه کار:

  1. پردازش رویداد ناهمزمان:
    • در یک سیستم رویداد محور، رویدادها (مانند دریافت پیام از یک صف، کلیک کاربر، یا تغییر وضعیت سیستم) می‌توانند باعث فعال شدن پردازشگرهای رویداد (Event Handlers) شوند.
    • با استفاده از async/await در این پردازشگرها، می‌توانید عملیات‌های طولانی مدت (مانند فراخوانی API، دسترسی به دیتابیس) را بدون بلاک کردن thread اصلی که مسئول دریافت و توزیع رویدادها است، انجام دهید.
  2. مقیاس‌پذیری و پاسخگویی:
    • این رویکرد به سیستم اجازه می‌دهد تا تعداد بیشتری رویداد را به صورت همزمان پردازش کند و پاسخگویی کلی سیستم را بهبود بخشد.
  3. مدیریت جریان:
    • async/await به شما کمک می‌کند تا جریان کنترل را در عملیات‌های ناهمزمان به صورت خطی و قابل فهم نگه دارید، حتی زمانی که چندین عملیات ناهمزمان به صورت زنجیره‌ای یا موازی اجرا می‌شوند.

مثال در یک سیستم صف پیام (Message Queue):

public class MessageProcessor
{
    public async Task ProcessMessageAsync(Message message)
    {
        Console.WriteLine($"Processing message {message.Id} on thread {Thread.CurrentThread.ManagedThreadId}");

        // شبیه‌سازی عملیات طولانی مدت (مثلاً فراخوانی API خارجی)
        await CallExternalServiceAsync(message.Payload);

        Console.WriteLine($"Finished processing message {message.Id} on thread {Thread.CurrentThread.ManagedThreadId}");
    }

    private async Task CallExternalServiceAsync(string payload)
    {
        // فرض کنید این یک فراخوانی HTTP به یک سرویس خارجی است
        using (var client = new HttpClient())
        {
            await client.PostAsync("http://external.service/api/process", new StringContent(payload));
        }
    }

    // متدی که پیام‌ها را از صف دریافت می‌کند و پردازشگر را فراخوانی می‌کند
    public async Task StartListeningToQueueAsync()
    {
        // شبیه‌سازی دریافت پیام از صف
        await foreach (var message in GetMessagesFromQueueAsync())
        {
            // هر پیام را به صورت ناهمزمان پردازش می‌کنیم
            await ProcessMessageAsync(message);
        }
    }

    private async IAsyncEnumerable<Message> GetMessagesFromQueueAsync()
    {
        for (int i = 0; i < 5; i++)
        {
            await Task.Delay(500); // شبیه‌سازی تاخیر در دریافت پیام
            yield return new Message { Id = i + 1, Payload = $"Data for message {i + 1}" };
        }
    }
}

public class Message
{
    public int Id { get; set; }
    public string Payload { get; set; }
}

نکات مهم:

سوال 33: async و await در Microservices چگونه کار می‌کنند؟

پاسخ: async/await یک ابزار ضروری در توسعه Microservices (میکروسرویس‌ها) است. در معماری میکروسرویس، برنامه‌ها به مجموعه‌ای از سرویس‌های کوچک و مستقل تقسیم می‌شوند که از طریق شبکه با یکدیگر ارتباط برقرار می‌کنند. این ارتباطات معمولاً شامل فراخوانی‌های HTTP یا gRPC است که ذاتاً ناهمزمان هستند.

نحوه کار در Microservices:

  1. بهبود مقیاس‌پذیری:
    • هر میکروسرویس می‌تواند تعداد زیادی درخواست همزمان را مدیریت کند. با استفاده از async/await، thread‌های سرور در حین انتظار برای پاسخ از سایر سرویس‌ها یا دیتابیس‌ها آزاد می‌شوند.
    • این بدان معناست که یک سرویس می‌تواند با تعداد کمتری thread، تعداد بیشتری درخواست را پردازش کند، که منجر به استفاده بهینه از منابع و مقیاس‌پذیری بهتر می‌شود.
  2. کاهش Latency:
    • عملیات‌های ناهمزمان به سرویس‌ها اجازه می‌دهند تا چندین کار را به صورت موازی انجام دهند. به عنوان مثال، یک سرویس می‌تواند همزمان چندین فراخوانی به سرویس‌های دیگر یا دیتابیس‌ها را آغاز کند و منتظر تکمیل همه آن‌ها بماند.
    • این کار می‌تواند به کاهش زمان پاسخگویی کلی (Latency) برای درخواست‌های پیچیده کمک کند.
  3. پاسخگویی:
    • سرویس‌ها حتی در حین انجام عملیات‌های طولانی مدت، پاسخگو باقی می‌مانند و می‌توانند به سرعت به درخواست‌های جدید پاسخ دهند.
  4. مدیریت خطا:
    • async/await به شما امکان می‌دهد تا مدیریت خطا را در عملیات‌های ناهمزمان به صورت ساختاریافته انجام دهید.

مثال در یک میکروسرویس (ASP.NET Core Web API):

public class OrderService : ControllerBase
{
    private readonly ProductService _productService;
    private readonly InventoryService _inventoryService;

    public OrderService(ProductService productService, InventoryService inventoryService)
    {
        _productService = productService;
        _inventoryService = inventoryService;
    }

    [HttpPost("createOrder")]
    public async Task<IActionResult> CreateOrder([FromBody] OrderRequest request)
    {
        // فراخوانی‌های ناهمزمان به سرویس‌های دیگر به صورت موازی
        var productTask = _productService.GetProductDetailsAsync(request.ProductId);
        var inventoryTask = _inventoryService.CheckInventoryAsync(request.ProductId, request.Quantity);

        await Task.WhenAll(productTask, inventoryTask);

        var product = await productTask;
        var inventory = await inventoryTask;

        if (product == null || !inventory.IsAvailable)
        {
            return BadRequest("Product not available or out of stock.");
        }

        // ادامه منطق ایجاد سفارش
        return Ok("Order created successfully.");
    }
}

public class ProductService
{
    public async Task<Product> GetProductDetailsAsync(string productId)
    {
        // شبیه‌سازی فراخوانی HTTP به سرویس محصول
        using (var client = new HttpClient())
        {
            var response = await client.GetAsync($"http://product.service/api/products/{productId}");
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadFromJsonAsync<Product>();
        }
    }
}

public class InventoryService
{
    public async Task<InventoryStatus> CheckInventoryAsync(string productId, int quantity)
    {
        // شبیه‌سازی فراخوانی HTTP به سرویس موجودی
        using (var client = new HttpClient())
        {
            var response = await client.GetAsync($"http://inventory.service/api/inventory/{productId}/{quantity}");
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadFromJsonAsync<InventoryStatus>();
        }
    }
}

public class OrderRequest
{
    public string ProductId { get; set; }
    public int Quantity { get; set; }
}

public class Product { /* ... */ }
public class InventoryStatus { public bool IsAvailable { get; set; } }

نکات مهم:

سوال 34: async و await در Domain-Driven Design (DDD) چگونه کار می‌کنند؟

پاسخ: در Domain-Driven Design (DDD)، async/await به شما امکان می‌دهد تا عملیات‌های ناهمزمان را به صورت طبیعی در مدل دامنه (Domain Model) و لایه‌های کاربردی (Application Layer) پیاده‌سازی کنید، بدون اینکه پیچیدگی‌های مدیریت thread را به مدل دامنه تحمیل کنید.

نحوه کار در DDD:

  1. Application Layer:
    • در لایه کاربردی، که مسئول هماهنگی بین مدل دامنه و زیرساخت است، async/await به طور گسترده‌ای برای فراخوانی عملیات‌های ناهمزمان از لایه زیرساخت (مانند دسترسی به دیتابیس، فراخوانی API خارجی) استفاده می‌شود.
    • این کار به لایه کاربردی اجازه می‌دهد تا درخواست‌ها را به صورت ناهمزمان پردازش کند و پاسخگویی سیستم را بهبود بخشد.
  2. Domain Model:
    • مدل دامنه (Aggregates, Entities, Value Objects) باید تا حد امکان خالص و بدون وابستگی به جزئیات زیرساخت باشد.
    • عملیات‌های ناهمزمان I/O-bound (مانند ذخیره داده در دیتابیس) نباید مستقیماً در مدل دامنه انجام شوند. در عوض، این عملیات‌ها باید به Repositoryها یا سرویس‌های دامنه (Domain Services) واگذار شوند که در لایه زیرساخت پیاده‌سازی می‌شوند.
    • با این حال، متدهای دامنه می‌توانند async باشند اگر نیاز به انجام عملیات‌های ناهمزمان CPU-bound یا هماهنگی با رویدادهای دامنه ناهمزمان داشته باشند.
  3. Infrastructure Layer:
    • لایه زیرساخت (Repositories, External Service Clients) جایی است که عملیات‌های ناهمزمان I/O-bound واقعی (مانند دسترسی به دیتابیس با Entity Framework Core، فراخوانی HTTP با HttpClient) پیاده‌سازی می‌شوند.
    • این لایه باید متدهای async را برای ارائه رابط ناهمزمان به لایه کاربردی ارائه دهد.

مثال در DDD:

// Application Layer
public class OrderApplicationService
{
    private readonly IOrderRepository _orderRepository;
    private readonly IProductService _productService;

    public OrderApplicationService(IOrderRepository orderRepository, IProductService productService)
    {
        _orderRepository = orderRepository;
        _productService = productService;
    }

    public async Task CreateOrderAsync(CreateOrderCommand command)
    {
        // فراخوانی ناهمزمان به سرویس محصول (Infrastructure Layer)
        var product = await _productService.GetProductByIdAsync(command.ProductId);
        if (product == null)
        {
            throw new ProductNotFoundException(command.ProductId);
        }

        // ایجاد Aggregate ریشه (Domain Model)
        var order = Order.CreateNew(command.CustomerId, product.Id, command.Quantity);

        // ذخیره ناهمزمان Aggregate (Infrastructure Layer)
        await _orderRepository.AddAsync(order);
        await _orderRepository.UnitOfWork.SaveEntitiesAsync();
    }
}

// Domain Model (مثال ساده)
public class Order : Entity, IAggregateRoot
{
    public Guid CustomerId { get; private set; }
    public Guid ProductId { get; private set; }
    public int Quantity { get; private set; }

    private Order() { } // برای ORM

    public static Order CreateNew(Guid customerId, Guid productId, int quantity)
    {
        // منطق دامنه
        return new Order { CustomerId = customerId, ProductId = productId, Quantity = quantity };
    }
}

// Infrastructure Layer (مثال ساده)
public interface IOrderRepository : IRepository<Order>
{
    Task AddAsync(Order order);
    Task<Order> GetByIdAsync(Guid id);
}

public interface IProductService
{
    Task<Product> GetProductByIdAsync(Guid productId);
}

public class ProductService : IProductService
{
    public async Task<Product> GetProductByIdAsync(Guid productId)
    {
        // شبیه‌سازی فراخوانی API خارجی
        await Task.Delay(500);
        return new Product { Id = productId, Name = "Sample Product" };
    }
}

نکات مهم:

سوال 35: async و await در Clean Architecture چگونه کار می‌کنند؟

پاسخ: در Clean Architecture، async/await به شما امکان می‌دهد تا عملیات‌های ناهمزمان را در لایه‌های مختلف معماری پیاده‌سازی کنید، در حالی که اصول جداسازی نگرانی‌ها و وابستگی به سمت داخل (Dependency Rule) را حفظ می‌کنید.

نحوه کار در Clean Architecture:

  1. Domain Layer (Entities, Use Cases/Interactors):
    • این لایه هسته برنامه است و باید مستقل از جزئیات پیاده‌سازی باشد.
    • Use Cases (یا Interactors) می‌توانند متدهای async داشته باشند، زیرا اغلب نیاز به هماهنگی با Gatewayها (که در لایه Infrastructure پیاده‌سازی می‌شوند) برای دسترسی به داده‌ها یا سرویس‌های خارجی دارند.
    • Entities باید تا حد امکان خالص باشند و نباید عملیات‌های I/O-bound ناهمزمان را مستقیماً انجام دهند.
  2. Application Layer (Controllers, Presenters):
    • این لایه مسئول ارائه رابط کاربری و هماهنگی با Use Cases است.
    • Controllers (در Web API) یا Presenters (در UI) می‌توانند متدهای async داشته باشند تا Use Cases ناهمزمان را فراخوانی کنند و نتایج را به صورت ناهمزمان به کاربر ارائه دهند.
  3. Infrastructure Layer (Gateways, External Services):
    • این لایه شامل جزئیات پیاده‌سازی (مانند دیتابیس، API خارجی، سیستم فایل) است.
    • Gatewayها (که رابط‌های آن‌ها در لایه Domain تعریف شده‌اند) عملیات‌های I/O-bound ناهمزمان را پیاده‌سازی می‌کنند و متدهای async را ارائه می‌دهند.

مثال در Clean Architecture:

// Domain Layer - Use Case
public class CreateOrderUseCase : ICreateOrderUseCase
{
    private readonly IOrderGateway _orderGateway;
    private readonly IProductGateway _productGateway;

    public CreateOrderUseCase(IOrderGateway orderGateway, IProductGateway productGateway)
    {
        _m_orderGateway = orderGateway;
        _productGateway = productGateway;
    }

    public async Task<OrderResponse> ExecuteAsync(CreateOrderRequest request)
    {
        var product = await _productGateway.GetProductByIdAsync(request.ProductId);
        if (product == null)
        {
            throw new ProductNotFoundException(request.ProductId);
        }

        var order = new Order(request.CustomerId, product.Id, request.Quantity);
        await _orderGateway.SaveOrderAsync(order);

        return new OrderResponse { OrderId = order.Id };
    }
}

// Domain Layer - Gateway Interface
public interface IOrderGateway
{
    Task SaveOrderAsync(Order order);
}

public interface IProductGateway
{
    Task<Product> GetProductByIdAsync(Guid productId);
}

// Infrastructure Layer - Gateway Implementation
public class OrderDatabaseGateway : IOrderGateway
{
    private readonly ApplicationDbContext _dbContext;

    public OrderDatabaseGateway(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task SaveOrderAsync(Order order)
    {
        _dbContext.Orders.Add(order);
        await _dbContext.SaveChangesAsync();
    }
}

public class ProductApiGateway : IProductGateway
{
    private readonly HttpClient _httpClient;

    public ProductApiGateway(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<Product> GetProductByIdAsync(Guid productId)
    {
        var response = await _httpClient.GetAsync($"api/products/{productId}");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<Product>();
    }
}

// Application Layer - Controller
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly ICreateOrderUseCase _createOrderUseCase;

    public OrdersController(ICreateOrderUseCase createOrderUseCase)
    {
        _createOrderUseCase = createOrderUseCase;
    }

    [HttpPost]
    public async Task<IActionResult> Create([FromBody] CreateOrderRequest request)
    {
        var response = await _createOrderUseCase.ExecuteAsync(request);
        return Ok(response);
    }
}

نکات مهم:

سوال 36: async و await در CQRS (Command Query Responsibility Segregation) چگونه کار می‌کنند؟

پاسخ: async/await به طور طبیعی با الگوی CQRS (Command Query Responsibility Segregation) سازگار است و به بهبود عملکرد و مقیاس‌پذیری در هر دو بخش Command و Query کمک می‌کند.

نحوه کار در CQRS:

  1. Command Side (Write Model):
    • در بخش Command، که مسئول تغییر وضعیت سیستم است، عملیات‌ها اغلب شامل ذخیره داده در دیتابیس یا انتشار رویدادها هستند. این عملیات‌ها معمولاً I/O-bound هستند.
    • Command Handlers می‌توانند async باشند تا عملیات‌های ناهمزمان را به صورت کارآمد انجام دهند و thread‌های سرور را آزاد کنند.
  2. Query Side (Read Model):
    • در بخش Query، که مسئول بازیابی داده‌ها است، عملیات‌ها اغلب شامل خواندن داده از دیتابیس یا کش هستند. این عملیات‌ها نیز معمولاً I/O-bound هستند.
    • Query Handlers می‌توانند async باشند تا داده‌ها را به صورت ناهمزمان بازیابی کنند و پاسخگویی برنامه را بهبود بخشند.

مثال در CQRS (با استفاده از MediatR):

// Command
public class CreateProductCommand : IRequest<Guid>
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// Command Handler
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, Guid>
{
    private readonly ApplicationDbContext _dbContext;

    public CreateProductCommandHandler(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<Guid> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        var product = new Product { Name = request.Name, Price = request.Price };
        _dbContext.Products.Add(product);
        await _dbContext.SaveChangesAsync(cancellationToken);
        return product.Id;
    }
}

// Query
public class GetProductByIdQuery : IRequest<ProductDto>
{
    public Guid Id { get; set; }
}

// Query Handler
public class GetProductByIdQueryHandler : IRequestHandler<GetProductByIdQuery, ProductDto>
{
    private readonly ApplicationDbContext _dbContext;

    public GetProductByIdQueryHandler(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<ProductDto> Handle(GetProductByIdQuery request, CancellationToken cancellationToken)
    {
        var product = await _dbContext.Products
            .AsNoTracking()
            .FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken);

        if (product == null)
        {
            return null;
        }

        return new ProductDto { Id = product.Id, Name = product.Name, Price = product.Price };
    }
}

// Controller
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IMediator _mediator;

    public ProductsController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IActionResult> Create([FromBody] CreateProductCommand command)
    {
        var productId = await _mediator.Send(command);
        return Ok(productId);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetById(Guid id)
    {
        var product = await _mediator.Send(new GetProductByIdQuery { Id = id });
        if (product == null)
        {
            return NotFound();
        }
        return Ok(product);
    }
}

نکات مهم:

سوال 37: async و await در Event Sourcing چگونه کار می‌کنند؟

پاسخ: async/await در Event Sourcing (رویداد مبنا) نقش مهمی ایفا می‌کند، به خصوص در سناریوهایی که نیاز به ذخیره و بازیابی رویدادها به صورت ناهمزمان وجود دارد. در Event Sourcing، به جای ذخیره وضعیت فعلی یک موجودیت، تمام تغییرات وضعیت به عنوان یک دنباله از رویدادها ذخیره می‌شوند.

نحوه کار در Event Sourcing:

  1. ذخیره رویداد ناهمزمان:
    • هنگامی که یک Command پردازش می‌شود و منجر به تغییر وضعیت می‌شود، یک یا چند Event تولید می‌شوند. این Eventها باید در یک Event Store ذخیره شوند.
    • عملیات ذخیره Event در Event Store (که معمولاً یک دیتابیس یا سیستم ذخیره‌سازی توزیع شده است) یک عملیات I/O-bound است. استفاده از async/await در این عملیات به بهبود مقیاس‌پذیری و پاسخگویی سیستم کمک می‌کند.
  2. بازیابی رویداد ناهمزمان:
    • برای بازسازی وضعیت یک موجودیت، باید تمام Eventهای مربوط به آن موجودیت از Event Store بازیابی شوند.
    • این عملیات نیز I/O-bound است و می‌تواند به صورت ناهمزمان با async/await انجام شود.
  3. پردازش رویداد ناهمزمان:
    • Eventها می‌توانند توسط Event Handlerها پردازش شوند تا Read Modelها را به‌روزرسانی کنند یا عملیات‌های جانبی را انجام دهند. این پردازشگرها نیز می‌توانند async باشند.

مثال در Event Sourcing:

// Aggregate Root
public class Account : AggregateRoot
{
    public Guid AccountId { get; private set; }
    public decimal Balance { get; private set; }

    public Account() { }

    public Account(Guid accountId)
    {
        ApplyChange(new AccountCreatedEvent(accountId));
    }

    public void Deposit(decimal amount)
    {
        if (amount <= 0) throw new ArgumentException("Deposit amount must be positive.");
        ApplyChange(new FundsDepositedEvent(AccountId, amount));
    }

    protected override void Apply(DomainEvent @event)
    {
        switch (@event)
        {
            case AccountCreatedEvent ace: AccountId = ace.AccountId;
                break;
            case FundsDepositedEvent fde: Balance += fde.Amount;
                break;
        }
    }
}

// Event Store Interface
public interface IEventStore
{
    Task SaveEventsAsync(Guid aggregateId, IEnumerable<DomainEvent> events, int expectedVersion);
    Task<IEnumerable<DomainEvent>> GetEventsForAggregateAsync(Guid aggregateId);
}

// Event Store Implementation (مثال ساده)
public class InMemoryEventStore : IEventStore
{
    private readonly Dictionary<Guid, List<DomainEvent>> _events = new Dictionary<Guid, List<DomainEvent>>();

    public Task SaveEventsAsync(Guid aggregateId, IEnumerable<DomainEvent> events, int expectedVersion)
    {
        if (!_events.ContainsKey(aggregateId))
        {
            _events.Add(aggregateId, new List<DomainEvent>());
        }

        // منطق بررسی نسخه و Conflict Resolution

        _events[aggregateId].AddRange(events);
        return Task.CompletedTask;
    }

    public Task<IEnumerable<DomainEvent>> GetEventsForAggregateAsync(Guid aggregateId)
    {
        return Task.FromResult<IEnumerable<DomainEvent>>(_events.GetValueOrDefault(aggregateId, new List<DomainEvent>()));
    }
}

// Application Service
public class AccountApplicationService
{
    private readonly IEventStore _eventStore;

    public AccountApplicationService(IEventStore eventStore)
    {
        _eventStore = eventStore;
    }

    public async Task CreateAccount(Guid accountId)
    {
        var account = new Account(accountId);
        await _eventStore.SaveEventsAsync(accountId, account.GetUncommittedChanges(), -1);
    }

    public async Task DepositFunds(Guid accountId, decimal amount)
    {
        var events = await _eventStore.GetEventsForAggregateAsync(accountId);
        var account = new Account();
        account.LoadsFromHistory(events);

        account.Deposit(amount);
        await _eventStore.SaveEventsAsync(accountId, account.GetUncommittedChanges(), account.Version);
    }
}

نکات مهم:

سوال 38: async و await در Actor Model چگونه کار می‌کنند؟

پاسخ: async/await به طور طبیعی با Actor Model (مدل بازیگر) سازگار است و به پیاده‌سازی سیستم‌های توزیع شده و همزمان کمک می‌کند. در Actor Model، بازیگرها (Actors) واحدهای مستقل و ایزوله از محاسبات هستند که با ارسال پیام با یکدیگر ارتباط برقرار می‌کنند.

نحوه کار در Actor Model:

  1. پردازش پیام ناهمزمان:
    • بازیگرها پیام‌ها را از یک صندوق پستی (Mailbox) دریافت می‌کنند و آن‌ها را به صورت تک thread پردازش می‌کنند.
    • هنگامی که یک بازیگر نیاز به انجام یک عملیات I/O-bound (مانند فراخوانی دیتابیس یا سرویس خارجی) دارد، می‌تواند از async/await استفاده کند. این کار به بازیگر اجازه می‌دهد تا در حین انتظار برای تکمیل عملیات I/O، thread خود را آزاد کند و پیام‌های دیگر را پردازش کند (اگرچه در برخی پیاده‌سازی‌ها، بازیگر ممکن است تا تکمیل await بلاک شود).
  2. ارسال پیام ناهمزمان:
    • ارسال پیام بین بازیگرها معمولاً یک عملیات ناهمزمان است.
  3. پاسخگویی و مقیاس‌پذیری:
    • با استفاده از async/await، بازیگرها می‌توانند پاسخگو باقی بمانند و به طور موثرتری از منابع استفاده کنند، که منجر به بهبود مقیاس‌پذیری سیستم می‌شود.

مثال در Akka.NET:

public class MyActor : ReceiveActor
{
    private readonly IHttpClientFactory _httpClientFactory;

    public MyActor(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
        ReceiveAsync<string>(async message =>
        {
            Console.WriteLine($"Actor received: {message} on thread {Thread.CurrentThread.ManagedThreadId}");
            await ProcessMessageAsync(message);
            Console.WriteLine($"Actor finished processing: {message} on thread {Thread.CurrentThread.ManagedThreadId}");
        });
    }

    private async Task ProcessMessageAsync(string message)
    {
        // شبیه‌سازی عملیات I/O-bound
        using (var client = _httpClientFactory.CreateClient())
        {
            var response = await client.GetStringAsync("http://example.com");
            Console.WriteLine($"Downloaded data: {response.Length} bytes");
        }
    }
}

// نحوه ایجاد و ارسال پیام به Actor
public class Program
{
    public static async Task Main(string[] args)
    {
        var system = ActorSystem.Create("MyActorSystem");
        var actor = system.ActorOf(Props.Create(() => new MyActor(new HttpClientFactory())), "myActor");

        actor.Tell("Hello from Main!");
        actor.Tell("Another message!");

        await Task.Delay(5000); // منتظر می‌مانیم تا Actor پیام‌ها را پردازش کند
        await system.Terminate();
    }

    // یک پیاده‌سازی ساده از IHttpClientFactory برای مثال
    public class HttpClientFactory : IHttpClientFactory
    {
        public HttpClient CreateClient(string name = null)
        {
            return new HttpClient();
        }
    }
}

نکات مهم:

سوال 39: async و await در Functional Programming چگونه کار می‌کنند؟

پاسخ: async/await می‌تواند با اصول Functional Programming (برنامه‌نویسی تابعی) ترکیب شود تا کدهای ناهمزمان را به صورت تمیزتر و قابل ترکیب‌تر بنویسید. در برنامه‌نویسی تابعی، تمرکز بر توابع خالص (Pure Functions)، عدم تغییرپذیری (Immutability) و ترکیب توابع است.

نحوه کار:

  1. توابع خالص ناهمزمان:
    • می‌توانید توابع async بنویسید که خالص باشند (یعنی بدون عوارض جانبی و با ورودی یکسان، خروجی یکسان تولید کنند). این توابع Task<T> را برمی‌گردانند.
  2. ترکیب توابع ناهمزمان:
    • با استفاده از await و متدهای کمکی Task (مانند ContinueWith, WhenAll, WhenAny)، می‌توانید توابع ناهمزمان را به صورت زنجیره‌ای یا موازی ترکیب کنید.
  3. عدم تغییرپذیری:
    • در کدهای ناهمزمان، حفظ عدم تغییرپذیری داده‌ها به جلوگیری از مشکلات همزمانی کمک می‌کند.

مثال در F# (که از async/await پشتیبانی می‌کند):

open System
open System.Net.Http

// یک تابع خالص ناهمزمان
let downloadContentAsync (url: string) : Async<string> =
    async {
        use client = new HttpClient()
        let! content = client.GetStringAsync(url) |> Async.AwaitTask
        return content
    }

// ترکیب توابع ناهمزمان
let processUrlsAsync (urls: string list) : Async<string list> =
    async {
        let! contents = urls
                       |> List.map downloadContentAsync
                       |> Async.Parallel
                       |> Async.RunSynchronously // در اینجا برای سادگی از RunSynchronously استفاده شده است
        return contents |> List.map (fun s -> s.Substring(0, 50))
    }

// نحوه استفاده
[]
let main argv =
    let urls = ["http://example.com"; "http://google.com"]
    let! results = processUrlsAsync urls

    results |> List.iter (printfn "%s")

    0

نکات مهم:

سوال 40: async و await در Reactive Programming (Rx) چگونه کار می‌کنند؟

پاسخ: async/await و Reactive Programming (Rx) هر دو برای مدیریت عملیات‌های ناهمزمان و رویداد محور طراحی شده‌اند، اما رویکردهای متفاوتی دارند. async/await برای مدیریت یک عملیات ناهمزمان واحد که یک نتیجه را برمی‌گرداند مناسب است، در حالی که Rx برای مدیریت جریان‌های داده (Data Streams) و رویدادهای متعدد در طول زمان مناسب است.

نحوه ترکیب:

  1. تبدیل Task به Observable:
    • می‌توانید یک Task را به یک Observable تبدیل کنید (با استفاده از Observable.FromAsync یا Observable.StartAsync) تا نتیجه یک عملیات async/await را در یک جریان Rx ادغام کنید.
  2. تبدیل Observable به Task:
    • می‌توانید یک Observable را به یک Task تبدیل کنید (با استفاده از ToTask()) تا آخرین مقدار یا اولین مقدار یک جریان را به عنوان یک Task دریافت کنید.
  3. استفاده از await در داخل Subscribe:
    • می‌توانید از await در داخل بلوک Subscribe یک Observable استفاده کنید تا عملیات‌های ناهمزمان را برای هر عنصر در جریان انجام دهید.

مثال ترکیب Rx و async/await:

public class DataService
{
    public async Task<string> GetRemoteDataAsync(int id)
    {
        await Task.Delay(1000); // شبیه‌سازی فراخوانی شبکه
        return $"Data from remote service for ID: {id}";
    }
}

public class Program
{
    public static async Task Main(string[] args)
    {
        var service = new DataService();

        // 1. تبدیل Task به Observable
        var observableFromTask = Observable.FromAsync(() => service.GetRemoteDataAsync(1));
        observableFromTask.Subscribe(data => Console.WriteLine($"Observable from Task: {data}"));

        // 2. استفاده از await در داخل Subscribe
        var ids = new[] { 2, 3, 4 };
        var observableIds = ids.ToObservable();

        observableIds.SelectMany(async id => await service.GetRemoteDataAsync(id))
                     .Subscribe(data => Console.WriteLine($"Observable with await in SelectMany: {data}"));

        // 3. تبدیل Observable به Task
        var lastDataTask = observableIds.SelectMany(async id => await service.GetRemoteDataAsync(id))
                                        .ToTask();
        var result = await lastDataTask;
        Console.WriteLine($"Last data from Observable to Task: {result}");

        Console.ReadKey();
    }
}

نکات مهم:

سوال 41: async و await در GraphQL چگونه کار می‌کنند؟

پاسخ: async/await در GraphQL (به خصوص در پیاده‌سازی‌های سمت سرور مانند Hot Chocolate یا GraphQL.NET) نقش مهمی ایفا می‌کند تا عملیات‌های بازیابی داده را به صورت ناهمزمان و کارآمد انجام دهد. GraphQL یک زبان کوئری برای APIها و یک Runtime برای اجرای آن کوئری‌ها با داده‌های موجود شما است.

نحوه کار در GraphQL:

  1. Resolvers ناهمزمان:
    • در GraphQL، هر فیلد در Schema دارای یک Resolver است که مسئول بازیابی داده برای آن فیلد است.
    • بسیاری از عملیات‌های بازیابی داده (مانند دسترسی به دیتابیس، فراخوانی Microservice دیگر، یا API خارجی) ذاتاً ناهمزمان هستند.
    • Resolvers می‌توانند async باشند و از await برای فراخوانی عملیات‌های ناهمزمان استفاده کنند. این کار به سرور GraphQL اجازه می‌دهد تا در حین انتظار برای داده‌ها، thread‌های خود را آزاد کند و درخواست‌های دیگر را پردازش کند.
  2. N+1 Problem:
    • async/await به تنهایی N+1 Problem را حل نمی‌کند، اما با ترکیب آن با الگوهایی مانند DataLoader (در Hot Chocolate) یا Batching، می‌توانید عملیات‌های بازیابی داده را به صورت بهینه و ناهمزمان انجام دهید.

مثال در Hot Chocolate (GraphQL for .NET):

public class Query
{
    public async Task<Book> GetBook(Guid id, [Service] IBookRepository bookRepository)
    {
        // فراخوانی ناهمزمان به دیتابیس یا سرویس دیگر
        return await bookRepository.GetBookByIdAsync(id);
    }

    public async Task<IEnumerable<Author>> GetAuthors([Service] IAuthorRepository authorRepository)
    {
        return await authorRepository.GetAllAuthorsAsync();
    }
}

public class Book
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public Guid AuthorId { get; set; }

    // Resolver برای فیلد Author
    public async Task<Author> GetAuthor([Parent] Book book, [Service] IAuthorRepository authorRepository)
    {
        // این فراخوانی می‌تواند منجر به N+1 Problem شود اگر به درستی مدیریت نشود
        return await authorRepository.GetAuthorByIdAsync(book.AuthorId);
    }
}

// Repository Interfaces
public interface IBookRepository
{
    Task<Book> GetBookByIdAsync(Guid id);
}

public interface IAuthorRepository
{
    Task<Author> GetAuthorByIdAsync(Guid id);
    Task<IEnumerable<Author>> GetAllAuthorsAsync();
}

// Repository Implementations (مثال ساده)
public class BookRepository : IBookRepository
{
    public async Task<Book> GetBookByIdAsync(Guid id)
    {
        await Task.Delay(100); // شبیه‌سازی دسترسی به دیتابیس
        return new Book { Id = id, Title = $"Book {id}", AuthorId = Guid.NewGuid() };
    }
}

public class AuthorRepository : IAuthorRepository
{
    public async Task<Author> GetAuthorByIdAsync(Guid id)
    {
        await Task.Delay(100); // شبیه‌سازی دسترسی به دیتابیس
        return new Author { Id = id, Name = $"Author {id}" };
    }

    public async Task<IEnumerable<Author>> GetAllAuthorsAsync()
    {
        await Task.Delay(200); // شبیه‌سازی دسترسی به دیتابیس
        return new List<Author>
        {
            new Author { Id = Guid.NewGuid(), Name = "Author 1" },
            new Author { Id = Guid.NewGuid(), Name = "Author 2" }
        };
    }
}

نکات مهم:

سوال 42: async و await در gRPC چگونه کار می‌کنند؟

پاسخ: async/await به طور طبیعی با gRPC (یک فریم‌ورک RPC با کارایی بالا) سازگار است و به پیاده‌سازی سرویس‌های gRPC به صورت ناهمزمان و کارآمد کمک می‌کند. gRPC از HTTP/2 برای انتقال و Protocol Buffers برای سریال‌سازی استفاده می‌کند.

نحوه کار در gRPC:

  1. Service Implementations ناهمزمان:
    • متدهای سرویس در gRPC می‌توانند async باشند و Task<TResponse> را برگردانند.
    • این به سرور gRPC اجازه می‌دهد تا در حین انجام عملیات‌های I/O-bound (مانند دسترسی به دیتابیس، فراخوانی سرویس‌های دیگر)، thread‌های خود را آزاد کند و درخواست‌های دیگر را پردازش کند.
  2. Client-side ناهمزمان:
    • کلاینت‌های gRPC نیز می‌توانند متدهای async را برای فراخوانی سرویس‌ها به صورت ناهمزمان ارائه دهند.
  3. Streaming:
    • gRPC از چهار نوع Streaming پشتیبانی می‌کند: Unary (تک درخواست/تک پاسخ)، Server Streaming، Client Streaming و Bidirectional Streaming.
    • async/await برای مدیریت جریان‌های داده در Streaming gRPC بسیار مفید است.

مثال در gRPC (C#):

// Service Definition (in .proto file)
// service Greeter {
//   rpc SayHello (HelloRequest) returns (HelloReply);
//   rpc SayHelloStream (HelloRequest) returns (stream HelloReply);
// }

// Server Implementation
public class GreeterService : Greeter.GreeterBase
{
    private readonly ILogger<GreeterService> _logger;

    public GreeterService(ILogger<GreeterService> logger)
    {
        _logger = logger;
    }

    public override async Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
        _logger.LogInformation($"SayHello received: {request.Name}");
        await Task.Delay(100); // شبیه‌سازی عملیات I/O
        return new HelloReply { Message = $"Hello {request.Name}" };
    }

    public override async Task SayHelloStream(HelloRequest request, IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
    {
        _logger.LogInformation($"SayHelloStream received: {request.Name}");
        for (int i = 0; i < 5; i++)
        {
            await responseStream.WriteAsync(new HelloReply { Message = $"Hello {request.Name} ({i + 1})" });
            await Task.Delay(500); // شبیه‌سازی تاخیر
        }
    }
}

// Client Usage
public class GrpcClient
{
    public static async Task RunAsync()
    {
        using var channel = GrpcChannel.ForAddress("https://localhost:5001");
        var client = new Greeter.GreeterClient(channel);

        // Unary Call
        var reply = await client.SayHelloAsync(new HelloRequest { Name = "World" });
        Console.WriteLine($"Greeting: {reply.Message}");

        // Server Streaming Call
        using var call = client.SayHelloStream(new HelloRequest { Name = "Streamer" });
        await foreach (var message in call.ResponseStream.ReadAllAsync())
        {
            Console.WriteLine($"Streamed Greeting: {message.Message}");
        }
    }
}

نکات مهم:

سوال 43: async و await در WebSockets چگونه کار می‌کنند؟

پاسخ: async/await به طور طبیعی با WebSockets (سوکت‌های وب) سازگار است و به پیاده‌سازی ارتباطات دوطرفه و ناهمزمان بین کلاینت و سرور کمک می‌کند. WebSockets یک پروتکل ارتباطی است که امکان ایجاد یک کانال ارتباطی تمام دوطرفه (Full-Duplex) را از طریق یک اتصال TCP فراهم می‌کند.

نحوه کار در WebSockets:

  1. دریافت و ارسال پیام ناهمزمان:
    • عملیات‌های دریافت و ارسال پیام از طریق WebSocket ذاتاً ناهمزمان هستند.
    • متدهایی مانند WebSocket.ReceiveAsync و WebSocket.SendAsync Task را برمی‌گردانند و می‌توانند با await استفاده شوند.
  2. مدیریت اتصال:
    • مدیریت چرخه حیات اتصال WebSocket (باز کردن، بستن، مدیریت خطا) نیز می‌تواند به صورت ناهمزمان انجام شود.
  3. مقیاس‌پذیری:
    • با استفاده از async/await، سرور می‌تواند تعداد زیادی اتصال WebSocket را بدون بلاک کردن thread‌ها مدیریت کند، که منجر به بهبود مقیاس‌پذیری می‌شود.

مثال در ASP.NET Core WebSockets:

// Startup.cs (Configure method)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...
    app.UseWebSockets();
    app.Use(async (context, next) =>
    {
        if (context.Request.Path == "/ws")
        {
            if (context.WebSockets.IsWebSocketRequest)
            {
                using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
                await Echo(webSocket);
            }
            else
            {
                context.Response.StatusCode = StatusCodes.Status400BadRequest;
            }
        }
        else
        {
            await next();
        }
    });
    // ...
}

private async Task Echo(WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    var receiveResult = await webSocket.ReceiveAsync(
        new ArraySegment<byte>(buffer), CancellationToken.None);

    while (!receiveResult.CloseStatus.HasValue)
    {
        await webSocket.SendAsync(
            new ArraySegment<byte>(buffer, 0, receiveResult.Count),
            receiveResult.MessageType,
            receiveResult.EndOfMessage,
            CancellationToken.None);

        receiveResult = await webSocket.ReceiveAsync(
            new ArraySegment<byte>(buffer), CancellationToken.None);
    }

    await webSocket.CloseAsync(
        receiveResult.CloseStatus.Value,
        receiveResult.CloseStatusDescription,
        CancellationToken.None);
}

نکات مهم:

سوال 44: async و await در SignalR چگونه کار می‌کنند؟

پاسخ: async/await به طور گسترده در SignalR (یک کتابخانه ASP.NET Core برای افزودن قابلیت‌های Real-time Web به برنامه‌ها) استفاده می‌شود. SignalR امکان ارسال پیام به کلاینت‌ها را به صورت Real-time فراهم می‌کند.

نحوه کار در SignalR:

  1. Hubs ناهمزمان:
    • متدهای Hub (که کلاینت‌ها می‌توانند آن‌ها را فراخوانی کنند) می‌توانند async باشند و Task را برگردانند.
    • این به SignalR اجازه می‌دهد تا در حین انجام عملیات‌های طولانی مدت (مانند دسترسی به دیتابیس، فراخوانی سرویس‌های دیگر)، thread‌های خود را آزاد کند و به کلاینت‌های دیگر پاسخ دهد.
  2. ارسال پیام ناهمزمان:
    • ارسال پیام از سرور به کلاینت‌ها (با استفاده از Clients.All.SendAsync یا Clients.Client(connectionId).SendAsync) نیز یک عملیات ناهمزمان است.
  3. مقیاس‌پذیری:
    • با استفاده از async/await، SignalR می‌تواند تعداد زیادی اتصال همزمان را مدیریت کند و مقیاس‌پذیری را بهبود بخشد.

مثال در SignalR Hub (C#):

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        // شبیه‌سازی ذخیره پیام در دیتابیس
        await Task.Delay(100);
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    public override async Task OnConnectedAsync()
    {
        await Clients.All.SendAsync("UserConnected", Context.ConnectionId);
        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception exception)
    {
        await Clients.All.SendAsync("UserDisconnected", Context.ConnectionId);
        await base.OnDisconnectedAsync(exception);
    }
}

نکات مهم:

سوال 45: async و await در Message Queues (RabbitMQ, Kafka) چگونه کار می‌کنند؟

پاسخ: async/await به طور گسترده در تعامل با Message Queues (صف‌های پیام) مانند RabbitMQ و Kafka استفاده می‌شود. این ترکیب به شما امکان می‌دهد تا پیام‌ها را به صورت ناهمزمان ارسال و دریافت کنید، که برای ساخت سیستم‌های توزیع شده و مقیاس‌پذیر ضروری است.

نحوه کار:

  1. ارسال پیام ناهمزمان (Producer):
    • هنگامی که یک برنامه نیاز به ارسال پیام به یک صف دارد، عملیات ارسال می‌تواند I/O-bound باشد (به دلیل ارتباط شبکه با Message Broker).
    • با استفاده از متدهای async برای ارسال پیام، thread ارسال کننده آزاد می‌شود و می‌تواند کارهای دیگری را انجام دهد.
  2. دریافت پیام ناهمزمان (Consumer):
    • در سمت مصرف‌کننده، پردازش پیام‌ها نیز می‌تواند شامل عملیات‌های I/O-bound (مانند ذخیره در دیتابیس، فراخوانی API) باشد.
    • Consumerها می‌توانند async باشند تا پیام‌ها را به صورت ناهمزمان پردازش کنند و از بلاک شدن thread‌های مصرف‌کننده جلوگیری کنند.
  3. مقیاس‌پذیری و پاسخگویی:
    • این رویکرد به سیستم اجازه می‌دهد تا تعداد بیشتری پیام را به صورت همزمان پردازش کند و پاسخگویی کلی سیستم را بهبود بخشد.

مثال با RabbitMQ (با استفاده از RabbitMQ.Client):

// Producer
public class RabbitMqProducer
{
    private readonly IConnection _connection;
    private readonly IModel _channel;

    public RabbitMqProducer(string hostname)
    {
        var factory = new ConnectionFactory() { HostName = hostname };
        _connection = factory.CreateConnection();
        _channel = _connection.CreateModel();
        _channel.QueueDeclare(queue: "hello", durable: false, exclusive: false, autoDelete: false, arguments: null);
    }

    public async Task PublishMessageAsync(string message)
    {
        var body = Encoding.UTF8.GetBytes(message);
        // PublishAsync یک متد ناهمزمان است
        await Task.Run(() => _channel.BasicPublish(exchange: "", routingKey: "hello", basicProperties: null, body: body));
        Console.WriteLine($" [x] Sent {message}");
    }

    public void Close()
    {
        _channel.Close();
        _connection.Close();
    }
}

// Consumer
public class RabbitMqConsumer
{
    private readonly IConnection _connection;
    private readonly IModel _channel;

    public RabbitMqConsumer(string hostname)
    {
        var factory = new ConnectionFactory() { HostName = hostname };
        _connection = factory.CreateConnection();
        _channel = _connection.CreateModel();
        _channel.QueueDeclare(queue: "hello", durable: false, exclusive: false, autoDelete: false, arguments: null);
    }

    public void StartConsuming()
    {
        var consumer = new EventingBasicConsumer(_channel);
        consumer.Received += async (model, ea) =>
        {
            var body = ea.Body.ToArray();
            var message = Encoding.UTF8.GetString(body);
            Console.WriteLine($" [x] Received {message}");

            // پردازش پیام به صورت ناهمزمان
            await ProcessMessageAsync(message);

            _channel.BasicAck(ea.DeliveryTag, false);
        };
        _channel.BasicConsume(queue: "hello", autoAck: false, consumer: consumer);
    }

    private async Task ProcessMessageAsync(string message)
    {
        // شبیه‌سازی عملیات طولانی مدت (مثلاً ذخیره در دیتابیس)
        await Task.Delay(1000);
        Console.WriteLine($"Processed message: {message}");
    }

    public void Close()
    {
        _channel.Close();
        _connection.Close();
    }
}

// Main Program
public class Program
{
    public static async Task Main(string[] args)
    {
        var producer = new RabbitMqProducer("localhost");
        var consumer = new RabbitMqConsumer("localhost");

        consumer.StartConsuming();

        await producer.PublishMessageAsync("Message 1");
        await producer.PublishMessageAsync("Message 2");

        Console.WriteLine("Press [enter] to exit.");
        Console.ReadLine();

        producer.Close();
        consumer.Close();
    }
}

نکات مهم:

سوال 46: async و await در Distributed Transactions چگونه کار می‌کنند؟

پاسخ: async/await به طور مستقیم Distributed Transactions (تراکنش‌های توزیع شده) را مدیریت نمی‌کند، اما می‌تواند در پیاده‌سازی الگوهایی که برای مدیریت تراکنش‌های توزیع شده استفاده می‌شوند، نقش داشته باشد. تراکنش‌های توزیع شده به معنای عملیات‌هایی هستند که چندین منبع داده (مانند چندین دیتابیس، صف پیام، یا سرویس خارجی) را درگیر می‌کنند و باید به صورت اتمی (Atomic) انجام شوند (یعنی یا همه موفق شوند یا هیچ کدام).

چالش‌ها:

نحوه کار با الگوهای تراکنش توزیع شده:

  1. Saga Pattern:
    • در الگوی Saga، یک تراکنش توزیع شده به مجموعه‌ای از تراکنش‌های محلی (Local Transactions) تقسیم می‌شود که هر کدام توسط یک سرویس جداگانه انجام می‌شوند.
    • Saga Orchestrator یا Saga Choreographer مسئول هماهنگی این تراکنش‌های محلی است.
    • عملیات‌های درگیر در Saga (مانند ارسال پیام، فراخوانی سرویس) می‌توانند async باشند.
  2. Outbox Pattern:
    • الگوی Outbox برای اطمینان از اتمی بودن ذخیره داده و انتشار رویداد استفاده می‌شود.
    • این الگو شامل ذخیره رویدادها در یک جدول Outbox در همان تراکنش دیتابیس که داده‌ها ذخیره می‌شوند، و سپس انتشار ناهمزمان آن‌ها به یک Message Broker است.

مثال با Saga Pattern (مفهومی):

// Order Saga Orchestrator
public class OrderSaga : IHandle<CreateOrderCommand>, IHandle<ProductReservedEvent>, IHandle<PaymentProcessedEvent>
{
    private readonly IMessageBus _messageBus;

    public OrderSaga(IMessageBus messageBus)
    {
        _messageBus = messageBus;
    }

    public async Task Handle(CreateOrderCommand command)
    {
        // شروع Saga: ارسال دستور رزرو محصول
        await _messageBus.PublishAsync(new ReserveProductCommand(command.ProductId, command.Quantity));
    }

    public async Task Handle(ProductReservedEvent @event)
    {
        // محصول رزرو شد: ارسال دستور پردازش پرداخت
        await _messageBus.PublishAsync(new ProcessPaymentCommand(@event.OrderId, @event.Amount));
    }

    public async Task Handle(PaymentProcessedEvent @event)
    {
        // پرداخت انجام شد: ارسال دستور نهایی کردن سفارش
        await _messageBus.PublishAsync(new FinalizeOrderCommand(@event.OrderId));
    }

    // متدهای مدیریت خطای جبرانی (Compensation) نیز باید پیاده‌سازی شوند
}

// Message Bus Interface
public interface IMessageBus
{
    Task PublishAsync<T>(T message);
}

// Message Bus Implementation (مثال ساده)
public class InMemoryMessageBus : IMessageBus
{
    public Task PublishAsync<T>(T message)
    {
        Console.WriteLine($"Publishing message: {message.GetType().Name}");
        // در اینجا پیام به Handlers مربوطه ارسال می‌شود
        return Task.CompletedTask;
    }
}

نکات مهم:

سوال 47: async و await در Serverless Architectures چگونه کار می‌کنند؟

پاسخ: async/await یک ابزار اساسی در Serverless Architectures (معماری‌های بدون سرور) است. در این معماری، شما کد خود را در توابع کوچک و مستقل (مانند Azure Functions, AWS Lambda) مستقر می‌کنید که توسط رویدادها (مانند درخواست HTTP، پیام صف، تغییر دیتابیس) فعال می‌شوند.

نحوه کار در Serverless Architectures:

  1. مقیاس‌پذیری خودکار:
    • توابع Serverless به طور خودکار بر اساس تقاضا مقیاس‌بندی می‌شوند.
    • با استفاده از async/await، هر اجرای تابع می‌تواند در حین انتظار برای عملیات‌های I/O-bound، thread خود را آزاد کند. این به پلتفرم Serverless اجازه می‌دهد تا تعداد بیشتری درخواست همزمان را با منابع کمتر مدیریت کند، که منجر به استفاده بهینه از منابع و کاهش هزینه‌ها می‌شود.
  2. پاسخگویی:
    • برای توابع HTTP Trigger، استفاده از async/await تضمین می‌کند که تابع در حین انتظار برای عملیات‌های خارجی، بلاک نمی‌شود و می‌تواند به سرعت به درخواست‌های جدید پاسخ دهد.
  3. مدیریت Context:
    • پلتفرم‌های Serverless به طور خودکار Contextهای لازم را در طول عملیات‌های ناهمزمان مدیریت می‌کنند.

مثال در AWS Lambda (C#):

public class Function
{
    public async Task<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
    {
        context.Logger.LogInformation("Processing a request.");

        string name = apigProxyEvent.QueryStringParameters?["name"];

        // شبیه‌سازی عملیات طولانی مدت I/O-bound
        string responseMessage = await CallExternalServiceAsync(name);

        return new APIGatewayProxyResponse
        {
            StatusCode = (int)HttpStatusCode.OK,
            Body = responseMessage,
            Headers = new Dictionary<string, string> { { "Content-Type", "text/plain" } }
        };
    }

    private async Task<string> CallExternalServiceAsync(string name)
    {
        // در اینجا می‌توانید یک فراخوانی واقعی به یک API خارجی یا دیتابیس داشته باشید
        await Task.Delay(1500); // شبیه‌سازی تاخیر شبکه
        return $"Hello, {name ?? "World"}. This Lambda function executed successfully asynchronously.";
    }
}

نکات مهم:

سوال 48: async و await در Edge Computing چگونه کار می‌کنند؟

پاسخ: async/await در Edge Computing (رایانش لبه) نقش مهمی ایفا می‌کند، به خصوص در سناریوهایی که نیاز به پردازش داده‌ها در نزدیکی منبع تولید آن‌ها (مانند دستگاه‌های IoT، سنسورها) وجود دارد. Edge Computing به کاهش Latency، کاهش پهنای باند و بهبود امنیت کمک می‌کند.

نحوه کار در Edge Computing:

  1. پردازش داده ناهمزمان:
    • دستگاه‌های لبه اغلب نیاز به پردازش داده‌ها از سنسورها یا سایر منابع به صورت ناهمزمان دارند.
    • با استفاده از async/await، می‌توان عملیات‌های I/O-bound (مانند خواندن از سنسور، ارسال داده به ابر) را بدون بلاک کردن thread اصلی دستگاه انجام داد.
  2. ارتباطات ناهمزمان:
    • ارتباط با سرویس‌های ابری یا سایر دستگاه‌های لبه معمولاً ناهمزمان است. async/await به مدیریت این ارتباطات کمک می‌کند.
  3. پاسخگویی و کارایی:
    • در محیط‌های با منابع محدود (مانند دستگاه‌های لبه)، استفاده بهینه از thread‌ها با async/await بسیار مهم است.

مثال در یک دستگاه Edge (مفهومی):

public class SensorDataProcessor
{
    private readonly ISensor _sensor;
    private readonly ICloudSyncService _cloudSyncService;

    public SensorDataProcessor(ISensor sensor, ICloudSyncService cloudSyncService)
    {
        _sensor = sensor;
        _cloudSyncService = cloudSyncService;
    }

    public async Task StartProcessingAsync()
    {
        await foreach (var data in _sensor.ReadDataStreamAsync())
        {
            var processedData = ProcessLocal(data);
            await _cloudSyncService.UploadDataAsync(processedData);
        }
    }

    private ProcessedData ProcessLocal(RawData data)
    {
        // انجام پردازش محلی (CPU-bound)
        return new ProcessedData { /* ... */ };
    }
}

public interface ISensor
{
    IAsyncEnumerable<RawData> ReadDataStreamAsync();
}

public interface ICloudSyncService
{
    Task UploadDataAsync(ProcessedData data);
}

نکات مهم:

سوال 49: async و await در Quantum Computing چگونه کار می‌کنند؟

پاسخ: در حال حاضر، async/await به طور مستقیم در Quantum Computing (رایانش کوانتومی) کاربرد ندارد. Quantum Computing یک حوزه کاملاً متفاوت از رایانش کلاسیک است که از اصول مکانیک کوانتومی برای حل مسائل پیچیده استفاده می‌کند.

چرا async/await در Quantum Computing کاربرد ندارد؟

نکات مرتبط (غیرمستقیم):

سوال 41: چگونه می‌توانیم از `async/await` برای بهبود پاسخگویی UI در برنامه‌های دسکتاپ استفاده کنیم؟

با استفاده از `async/await`، می‌توانیم عملیات طولانی‌مدت (مانند دسترسی به شبکه، عملیات دیسک، یا محاسبات سنگین) را در یک رشته پس‌زمینه اجرا کنیم، بدون اینکه UI اصلی مسدود شود. این کار باعث می‌شود که UI همچنان پاسخگو باقی بماند و کاربر بتواند با برنامه تعامل داشته باشد. برای مثال، در یک برنامه WPF یا WinForms، می‌توانیم یک دکمه را برای شروع یک عملیات `async` تعریف کنیم و نتیجه را پس از اتمام عملیات در UI نمایش دهیم.

سوال 42: تفاوت بین `Task.Run` و `await Task.Run` چیست؟

`Task.Run` یک عملیات را در یک رشته پس‌زمینه (از Thread Pool) اجرا می‌کند و یک `Task` را برمی‌گرداند که نشان‌دهنده آن عملیات است. `await Task.Run` علاوه بر اجرای عملیات در پس‌زمینه، منتظر می‌ماند تا آن عملیات به پایان برسد و سپس اجرای کد را از سر می‌گیرد. استفاده از `await` باعث می‌شود که کنترل به فراخواننده برگردد و UI مسدود نشود.

سوال 43: چگونه می‌توانیم از `async/await` در متدهای سازنده (Constructors) استفاده کنیم؟

متدهای سازنده نمی‌توانند `async` باشند. اگر نیاز به انجام عملیات `async` در سازنده دارید، می‌توانید یک متد `async` جداگانه ایجاد کنید و آن را از سازنده فراخوانی کنید. این متد `async` می‌تواند یک `Task` را برگرداند که می‌توانید آن را `await` کنید یا به صورت fire-and-forget اجرا کنید. اما باید مراقب باشید که اگر عملیات `async` در سازنده شکست بخورد، ممکن است برنامه به درستی شروع به کار نکند.

سوال 44: آیا می‌توانیم `async/await` را در حلقه‌ها (Loops) استفاده کنیم؟

بله، می‌توانیم `async/await` را در حلقه‌ها استفاده کنیم. این کار به ما اجازه می‌دهد تا عملیات `async` را به صورت متوالی یا موازی در یک حلقه اجرا کنیم. برای مثال، می‌توانیم یک لیست از URLها را به صورت `async` دانلود کنیم. اگر نیاز به اجرای موازی دارید، می‌توانید از `Task.WhenAll` استفاده کنید.

سوال 45: چگونه می‌توانیم از `async/await` برای مدیریت خطاها (Error Handling) استفاده کنیم؟

می‌توانیم از بلوک‌های `try-catch` برای مدیریت خطاها در متدهای `async` استفاده کنیم. هر استثنایی که در یک متد `async` رخ دهد، در `Task` برگشتی ذخیره می‌شود و زمانی که `Task` `await` شود، استثنا دوباره پرتاب می‌شود. این به ما اجازه می‌دهد تا خطاها را به همان روشی که در متدهای همزمان مدیریت می‌کنیم، مدیریت کنیم.

سوال 46: تفاوت بین `ConfigureAwait(false)` و `ConfigureAwait(true)` چیست؟

`ConfigureAwait(true)` (پیش‌فرض) تضمین می‌کند که ادامه اجرای کد پس از `await` در همان Context (مثلاً UI thread) که قبل از `await` بود، انجام شود. `ConfigureAwait(false)` این تضمین را نمی‌دهد و ادامه اجرای کد می‌تواند در هر Thread Pool thread انجام شود. استفاده از `ConfigureAwait(false)` می‌تواند عملکرد را در برنامه‌های غیر UI بهبود بخشد و از Deadlock جلوگیری کند.

سوال 47: چگونه می‌توانیم یک عملیات `async` را لغو (Cancel) کنیم؟

برای لغو یک عملیات `async`، می‌توانیم از `CancellationTokenSource` و `CancellationToken` استفاده کنیم. `CancellationTokenSource` برای ایجاد و مدیریت توکن لغو استفاده می‌شود و `CancellationToken` به متدهای `async` ارسال می‌شود. متدهای `async` می‌توانند وضعیت `CancellationToken` را بررسی کنند و در صورت درخواست لغو، عملیات را متوقف کنند.

سوال 48: آیا `async/await` جایگزین Threading است؟

خیر، `async/await` جایگزین Threading نیست، بلکه یک انتزاع (Abstraction) بالاتر از آن است. `async/await` به ما کمک می‌کند تا کد ناهمزمان را به روشی خواناتر و قابل نگهداری‌تر بنویسیم، بدون اینکه نیاز به مدیریت مستقیم Threadها داشته باشیم. در پشت صحنه، `async/await` از Thread Pool برای اجرای عملیات ناهمزمان استفاده می‌کند.

سوال 49: چگونه می‌توانیم از `async/await` در ASP.NET Core استفاده کنیم؟

در ASP.NET Core، استفاده از `async/await` بسیار رایج است و به بهبود مقیاس‌پذیری برنامه کمک می‌کند. با استفاده از `async/await` در Action Methodها، می‌توانیم درخواست‌های ورودی را بدون مسدود کردن Threadهای سرور پردازش کنیم، که این امر باعث می‌شود سرور بتواند درخواست‌های بیشتری را به صورت همزمان مدیریت کند. این کار به ویژه برای عملیات I/O-bound مانند دسترسی به پایگاه داده یا فراخوانی APIهای خارجی مفید است.