Menu

C# & ASP.NET Topic

C# Asynchronous Programming (async/await)

Fundamentals of async/await

Basic async/await Syntax

csharp

// Synchronous method
public string DownloadData(string url)
{
    using var client = new WebClient();
    return client.DownloadString(url); // Blocks thread
}

// Asynchronous method
public async Task<string> DownloadDataAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url); // Doesn't block thread
}

Async Method Patterns

csharp

public class AsyncExamples
{
    // Async method without return value
    public async Task DoWorkAsync()
    {
        await Task.Delay(1000);
        Console.WriteLine("Work completed");
    }

    // Async method with return value
    public async Task<int> CalculateAsync(int a, int b)
    {
        await Task.Delay(500);
        return a + b;
    }

    // Async method that returns completed task
    public Task<string> GetCachedDataAsync()
    {
        // If data is already available, return completed task
        if (_cache.TryGetValue("data", out string data))
            return Task.FromResult(data);
        
        // Otherwise, perform async operation
        return FetchDataAsync();
    }

    // ValueTask for performance (C# 7.0+)
    public async ValueTask<int> GetValueAsync()
    {
        if (_valueAvailable)
            return _cachedValue;
        
        return await ComputeValueAsync();
    }
}

Task Parallel Library (TPL)

Creating and Running Tasks

csharp

public class TaskCreationExamples
{
    // Task.Run for CPU-bound work
    public async Task<int> PerformCalculationAsync(int data)
    {
        // Offload CPU-intensive work to thread pool
        return await Task.Run(() => 
        {
            Thread.Sleep(1000); // Simulate CPU work
            return data * data;
        });
    }

    // Task.Factory for more control
    public Task<string> ProcessDataAsync(string input)
    {
        return Task.Factory.StartNew(() =>
        {
            // Long running task
            Thread.Sleep(2000);
            return input.ToUpper();
        }, TaskCreationOptions.LongRunning);
    }

    // Task.FromResult for immediate results
    public Task<bool> ValidateInputAsync(string input)
    {
        if (string.IsNullOrEmpty(input))
            return Task.FromResult(false);
        
        return Task.FromResult(input.Length > 5);
    }

    // Creating delayed tasks
    public async Task StartWithDelayAsync()
    {
        await Task.Delay(TimeSpan.FromSeconds(2));
        Console.WriteLine("Executed after 2 seconds");
    }
}

Task Continuations

csharp

public class TaskContinuationExamples
{
    public async Task ContinuationPatterns()
    {
        // Basic continuation
        Task<int> firstTask = Task.Run(() => 42);
        Task<string> continuation = firstTask.ContinueWith(previousTask =>
        {
            int result = previousTask.Result;
            return $"The answer is {result}";
        });
        
        string message = await continuation;

        // Multiple continuations
        Task<int> initialTask = Task.Run(() => 10);
        
        initialTask.ContinueWith(t => Console.WriteLine($"Success: {t.Result}"),
            TaskContinuationOptions.OnlyOnRanToCompletion);
            
        initialTask.ContinueWith(t => Console.WriteLine($"Failed: {t.Exception}"),
            TaskContinuationOptions.OnlyOnFaulted);

        // Async continuation with await
        Task<int> calculationTask = Task.Run(() => HeavyCalculation());
        await calculationTask.ContinueWith(async t =>
        {
            int result = t.Result;
            await SaveResultAsync(result);
        }, TaskContinuationOptions.OnlyOnRanToCompletion);
    }
}

Concurrency vs Parallelism

Concurrency (Async/Await)

csharp

public class ConcurrencyExamples
{
    // Concurrent execution - multiple operations interleaved
    public async Task ProcessMultipleOperationsAsync()
    {
        var task1 = DownloadFileAsync("http://example.com/file1.txt");
        var task2 = DownloadFileAsync("http://example.com/file2.txt");
        var task3 = DownloadFileAsync("http://example.com/file3.txt");

        // All tasks run concurrently
        string[] results = await Task.WhenAll(task1, task2, task3);
        
        Console.WriteLine($"Downloaded {results.Length} files");
    }

    // Sequential vs Concurrent
    public async Task SequentialProcessingAsync()
    {
        // Sequential - one after another
        var result1 = await ProcessItemAsync(1); // Waits for completion
        var result2 = await ProcessItemAsync(2); // Then starts
        var result3 = await ProcessItemAsync(3); // Then starts
    }

    public async Task ConcurrentProcessingAsync()
    {
        // Concurrent - all start together
        var task1 = ProcessItemAsync(1);
        var task2 = ProcessItemAsync(2);
        var task3 = ProcessItemAsync(3);

        // Wait for all to complete
        await Task.WhenAll(task1, task2, task3);
    }

    // Processing with throttling
    public async Task ProcessWithConcurrencyLimitAsync()
    {
        var items = Enumerable.Range(1, 100);
        var tasks = items.Select(ProcessItemAsync);
        
        // Process up to 5 concurrently
        var results = await ProcessWithMaxConcurrency(tasks, 5);
    }

    private async Task<T[]> ProcessWithMaxConcurrency<T>(
        IEnumerable<Task<T>> tasks, int maxConcurrency)
    {
        var allTasks = new List<Task<T>>();
        var pending = new Queue<Task<T>>(tasks);

        // Start initial batch
        while (pending.Count > 0 && allTasks.Count < maxConcurrency)
        {
            allTasks.Add(pending.Dequeue());
        }

        // Process as tasks complete
        while (allTasks.Count > 0)
        {
            var completed = await Task.WhenAny(allTasks);
            allTasks.Remove(completed);

            if (pending.Count > 0)
            {
                allTasks.Add(pending.Dequeue());
            }
        }

        return await Task.WhenAll(tasks);
    }
}

Parallelism (Parallel Class)

csharp

public class ParallelismExamples
{
    // CPU-bound parallel processing
    public void ProcessDataInParallel()
    {
        var data = Enumerable.Range(1, 1000).ToArray();
        
        // Parallel.For for data parallelism
        Parallel.For(0, data.Length, i =>
        {
            data[i] = ExpensiveCalculation(data[i]);
        });

        // Parallel.ForEach for collections
        Parallel.ForEach(data, item =>
        {
            ProcessItem(item);
        });

        // With options
        ParallelOptions options = new ParallelOptions
        {
            MaxDegreeOfParallelism = Environment.ProcessorCount - 1,
            CancellationToken = CancellationToken.None
        };

        Parallel.For(0, data.Length, options, i =>
        {
            // CPU-intensive work
        });
    }

    // PLINQ (Parallel LINQ)
    public void ProcessWithPLinq()
    {
        var numbers = Enumerable.Range(1, 1000000);
        
        var results = numbers
            .AsParallel()
            .Where(n => n % 2 == 0)
            .Select(n => ExpensiveTransformation(n))
            .WithDegreeOfParallelism(4)
            .WithExecutionMode(ParallelExecutionMode.ForceParallelism)
            .ToArray();
    }

    // When to use Parallel vs Async
    public async Task MixedUsage()
    {
        // Use Parallel for CPU-bound work
        await Task.Run(() =>
        {
            Parallel.For(0, 100, i =>
            {
                // CPU-intensive calculations
                Thread.SpinWait(1000000);
            });
        });

        // Use Async for I/O-bound work
        await DownloadMultipleFilesAsync();
    }
}

Task.Run - Proper Usage

Correct Usage Patterns

csharp

public class TaskRunExamples
{
    // CORRECT: Use Task.Run for CPU-bound work
    public async Task<int> CalculatePrimeAsync(int number)
    {
        return await Task.Run(() =>
        {
            // CPU-intensive calculation
            return FindNthPrime(number);
        });
    }

    // CORRECT: Mixed I/O and CPU-bound work
    public async Task<string> ProcessDataAsync(string input)
    {
        // I/O-bound operation (no Task.Run needed)
        string rawData = await DownloadDataAsync(input);
        
        // CPU-bound processing (use Task.Run)
        string processedData = await Task.Run(() => ProcessData(rawData));
        
        // Another I/O-bound operation
        await SaveDataAsync(processedData);
        
        return processedData;
    }

    // INCORRECT: Don't use Task.Run for I/O-bound async methods
    public async Task<string> BadExampleAsync(string url)
    {
        // WRONG - this just adds overhead
        return await Task.Run(async () => await _httpClient.GetStringAsync(url));
    }

    // CORRECT: For I/O-bound, just await directly
    public async Task<string> GoodExampleAsync(string url)
    {
        return await _httpClient.GetStringAsync(url);
    }
}

UI Applications with Task.Run

csharp

// In WPF, WinForms, or MAUI applications
public partial class MainWindow : Window
{
    private readonly HttpClient _httpClient = new HttpClient();

    // Keep UI responsive by offloading CPU work
    private async void ProcessButton_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            // Show loading indicator
            ProgressBar.Visibility = Visibility.Visible;
            
            // Offload CPU-intensive work to background thread
            var result = await Task.Run(() => PerformComplexCalculation());
            
            // Back on UI thread - update UI
            ResultTextBox.Text = result.ToString();
        }
        finally
        {
            ProgressBar.Visibility = Visibility.Hidden;
        }
    }

    //  I/O operations don't need Task.Run
    private async void DownloadButton_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            // No Task.Run needed for async I/O
            string data = await _httpClient.GetStringAsync("http://example.com/data");
            
            // We're back on UI thread automatically
            ContentTextBox.Text = data;
        }
        catch (Exception ex)
        {
            MessageBox.Show($"Download failed: {ex.Message}");
        }
    }

    private int PerformComplexCalculation()
    {
        // Simulate CPU-intensive work
        Thread.Sleep(3000);
        return Environment.TickCount;
    }
}

Deadlock Avoidance

Common Deadlock Scenarios

csharp

public class DeadlockExamples
{
    // DEADLOCK: Blocking on async code
    public string GetDataDeadlock()
    {
        var task = GetDataAsync();
        return task.Result; // Blocks, can cause deadlock
    }

    public async Task<string> GetDataAsync()
    {
        await Task.Delay(1000);
        return "Data";
    }

    // DEADLOCK: Using Wait() instead of await
    public void ProcessDataDeadlock()
    {
        var task = ProcessDataAsync();
        task.Wait(); // Blocks, can cause deadlock
    }

    //  SOLUTION: Use async all the way
    public async Task<string> GetDataCorrectAsync()
    {
        return await GetDataAsync();
    }

    public async Task ProcessDataCorrectAsync()
    {
        await ProcessDataAsync();
    }
}

ConfigureAwait(false) for Library Code

csharp

public class AsyncLibrary
{
    // Use ConfigureAwait(false) in library code
    public async Task<string> GetDataAsync()
    {
        // Don't capture context - can continue on any thread
        var data = await DownloadDataAsync().ConfigureAwait(false);
        
        // CPU-bound work
        return await Task.Run(() => ProcessData(data));
    }

    // When you need context, be explicit
    public async Task UpdateUIAsync()
    {
        var data = await GetDataAsync(); // No context captured
        
        // Explicitly switch to UI context if needed
        await Application.Current.Dispatcher.InvokeAsync(() =>
        {
            // Update UI elements
        });
    }

    private async Task<string> DownloadDataAsync()
    {
        using var client = new HttpClient();
        return await client.GetStringAsync("http://example.com")
            .ConfigureAwait(false); // Important for libraries
    }
}

Deadlock Prevention Patterns

csharp

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

    // DEADLOCK: Async lock with bad usage
    public async Task<string> GetDataWithDeadlockAsync()
    {
        await _semaphore.WaitAsync();
        try
        {
            // This can deadlock if called from UI thread
            return await FetchDataAsync();
        }
        finally
        {
            _semaphore.Release();
        }
    }

    // SOLUTION: Use ConfigureAwait(false) in locks
    public async Task<string> GetDataSafeAsync()
    {
        await _semaphore.WaitAsync().ConfigureAwait(false);
        try
        {
            return await FetchDataAsync().ConfigureAwait(false);
        }
        finally
        {
            _semaphore.Release();
        }
    }

    // BETTER: Use AsyncLock pattern
    public async Task<string> GetDataWithAsyncLock()
    {
        using (await _asyncLock.LockAsync())
        {
            return await FetchDataAsync();
        }
    }
}

// Async-friendly lock
public class AsyncLock
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
    private readonly Task<IDisposable> _releaser;

    public AsyncLock()
    {
        _releaser = Task.FromResult((IDisposable)new Releaser(this));
    }

    public Task<IDisposable> LockAsync()
    {
        var wait = _semaphore.WaitAsync();
        return wait.IsCompleted ?
            _releaser :
            wait.ContinueWith((_, state) => (IDisposable)state,
                _releaser.Result, CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
    }

    private class Releaser : IDisposable
    {
        private readonly AsyncLock _lock;
        public Releaser(AsyncLock @lock) => _lock = @lock;
        public void Dispose() => _lock._semaphore.Release();
    }
}

Advanced Patterns

Cancellation Support

csharp

public class CancellationExamples
{
    public async Task ProcessWithCancellationAsync()
    {
        var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
        
        try
        {
            await LongRunningOperationAsync(cts.Token);
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Operation was cancelled");
        }
    }

    public async Task LongRunningOperationAsync(CancellationToken cancellationToken = default)
    {
        // Check cancellation token periodically
        for (int i = 0; i < 100; i++)
        {
            cancellationToken.ThrowIfCancellationRequested();
            
            await Task.Delay(100, cancellationToken);
            await ProcessStepAsync(i, cancellationToken);
        }
    }

    // Cancellation with progress reporting
    public async Task<int> ProcessWithProgressAsync(
        IProgress<int> progress, 
        CancellationToken cancellationToken = default)
    {
        for (int i = 0; i <= 100; i++)
        {
            cancellationToken.ThrowIfCancellationRequested();
            
            await Task.Delay(50, cancellationToken);
            progress?.Report(i);
        }
        
        return 42;
    }
}

Task Combinators

csharp

public class TaskCombinators
{
    // Task.WhenAll - wait for all tasks
    public async Task ProcessAllAsync()
    {
        var tasks = new List<Task>
        {
            ProcessItemAsync(1),
            ProcessItemAsync(2),
            ProcessItemAsync(3)
        };

        await Task.WhenAll(tasks);
        Console.WriteLine("All tasks completed");
    }

    // Task.WhenAny - wait for first task to complete
    public async Task<string> GetFirstResponseAsync()
    {
        var tasks = new[]
        {
            FetchFromSource1Async(),
            FetchFromSource2Async(),
            FetchFromSource3Async()
        };

        Task<string> firstCompleted = await Task.WhenAny(tasks);
        return await firstCompleted;
    }

    // Timeout pattern
    public async Task<string> GetWithTimeoutAsync()
    {
        var workTask = LongRunningOperationAsync();
        var timeoutTask = Task.Delay(TimeSpan.FromSeconds(5));
        
        var completedTask = await Task.WhenAny(workTask, timeoutTask);
        
        if (completedTask == timeoutTask)
            throw new TimeoutException("Operation timed out");
        
        return await workTask;
    }

    // Retry pattern
    public async Task<T> RetryAsync<T>(Func<Task<T>> operation, int maxRetries = 3)
    {
        for (int attempt = 0; attempt < maxRetries; attempt++)
        {
            try
            {
                return await operation();
            }
            catch (Exception ex) when (attempt < maxRetries - 1)
            {
                Console.WriteLine($"Attempt {attempt + 1} failed: {ex.Message}");
                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); // Exponential backoff
            }
        }
        
        throw new InvalidOperationException("All retry attempts failed");
    }
}

ValueTask for Performance

csharp

public class ValueTaskExamples
{
    private string _cachedData;
    private DateTime _cacheTime = DateTime.MinValue;

    // Use ValueTask when result is often available synchronously
    public ValueTask<string> GetDataAsync()
    {
        // If data is cached and fresh, return synchronously
        if (_cachedData != null && DateTime.UtcNow - _cacheTime < TimeSpan.FromMinutes(5))
        {
            return new ValueTask<string>(_cachedData);
        }

        // Otherwise, perform async operation
        return new ValueTask<string>(FetchDataAsync());
    }

    // ValueTask for I/O that might be immediately available
    public ValueTask<byte[]> ReadDataAsync(string key)
    {
        if (_memoryCache.TryGetValue(key, out byte[] data))
        {
            return new ValueTask<byte[]>(data);
        }

        return new ValueTask<byte[]>(ReadFromDiskAsync(key));
    }

    private async Task<string> FetchDataAsync()
    {
        await Task.Delay(1000); // Simulate I/O
        _cachedData = "Fresh Data";
        _cacheTime = DateTime.UtcNow;
        return _cachedData;
    }
}

Real-World Examples

Web API Controller

csharp

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;
    private readonly ILogger<ProductsController> _logger;

    public ProductsController(IProductService productService, ILogger<ProductsController> logger)
    {
        _productService = productService;
        _logger = logger;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<Product>>> GetProducts(
        [FromQuery] int page = 1, 
        [FromQuery] int pageSize = 20,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // Run database queries concurrently
            var productsTask = _productService.GetProductsAsync(page, pageSize, cancellationToken);
            var totalCountTask = _productService.GetTotalCountAsync(cancellationToken);

            await Task.WhenAll(productsTask, totalCountTask);

            var products = await productsTask;
            var totalCount = await totalCountTask;

            Response.Headers.Add("X-Total-Count", totalCount.ToString());
            return Ok(products);
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Request was cancelled");
            return StatusCode(499); // Client Closed Request
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error fetching products");
            return StatusCode(500, "An error occurred");
        }
    }

    [HttpPost]
    public async Task<ActionResult<Product>> CreateProduct(
        [FromBody] CreateProductRequest request,
        CancellationToken cancellationToken = default)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);

        try
        {
            var product = await _productService.CreateProductAsync(request, cancellationToken);
            return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
        }
        catch (ArgumentException ex)
        {
            return BadRequest(ex.Message);
        }
    }
}

Background Service

csharp

public class DataProcessingService : BackgroundService
{
    private readonly ILogger<DataProcessingService> _logger;
    private readonly IServiceProvider _serviceProvider;
    private readonly PeriodicTimer _timer;

    public DataProcessingService(ILogger<DataProcessingService> logger, IServiceProvider serviceProvider)
    {
        _logger = logger;
        _serviceProvider = serviceProvider;
        _timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Data Processing Service started");

        while (await _timer.WaitForNextTickAsync(stoppingToken))
        {
            try
            {
                await ProcessPendingDataAsync(stoppingToken);
            }
            catch (OperationCanceledException)
            {
                _logger.LogInformation("Processing was cancelled");
                break;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing data");
            }
        }

        _logger.LogInformation("Data Processing Service stopped");
    }

    private async Task ProcessPendingDataAsync(CancellationToken cancellationToken)
    {
        using var scope = _serviceProvider.CreateScope();
        var processor = scope.ServiceProvider.GetRequiredService<IDataProcessor>();

        // Process items with parallelism and cancellation
        var pendingItems = await processor.GetPendingItemsAsync(cancellationToken);
        
        var processingTasks = pendingItems.Select(async item =>
        {
            try
            {
                await processor.ProcessItemAsync(item, cancellationToken);
                _logger.LogInformation("Processed item {ItemId}", item.Id);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing item {ItemId}", item.Id);
            }
        });

        // Limit concurrent processing
        await ProcessWithConcurrencyLimit(processingTasks, maxConcurrency: 5, cancellationToken);
    }

    public override void Dispose()
    {
        _timer?.Dispose();
        base.Dispose();
    }
}

Best Practices Summary

Do's and Don'ts

csharp

public class AsyncBestPractices
{
    // DO: Use async all the way
    public async Task<string> GoodMethodAsync()
    {
        return await SomeAsyncOperation();
    }

    // DON'T: Mix async and blocking code
    public string BadMethod()
    {
        return SomeAsyncOperation().Result; // DEADLOCK RISK
    }

    // DO: Use ConfigureAwait(false) in library code
    public async Task LibraryMethodAsync()
    {
        await SomeOperation().ConfigureAwait(false);
    }

    //  DO: Use cancellation tokens
    public async Task ProcessWithCancellationAsync(CancellationToken cancellationToken)
    {
        await Task.Delay(1000, cancellationToken);
        cancellationToken.ThrowIfCancellationRequested();
    }

    // DON'T: Use async void (except event handlers)
    public async void BadVoidAsync() // Exceptions can't be caught
    {
        await Task.Delay(1000);
    }

    // DO: Handle exceptions properly
    public async Task SafeMethodAsync()
    {
        try
        {
            await SomeOperationAsync();
        }
        catch (Exception ex)
        {
            // Log and handle appropriately
            _logger.LogError(ex, "Operation failed");
            throw;
        }
    }

    // DO: Use ValueTask for hot paths
    public ValueTask<int> EfficientMethodAsync()
    {
        if (_cacheValid)
            return new ValueTask<int>(_cachedValue);
        
        return new ValueTask<int>(ComputeValueAsync());
    }
}

This comprehensive guide covers all aspects of C# asynchronous programming, from basic async/await usage to advanced patterns and real-world applications!

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?