Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Mastering Task-Based Asynchronous Programming in C#

Tech 1

Understanding Task Parallel Library Fundamantals

Task Parallel Library (TPL) addresses critical limitations of traditional thread pool usage. While thread pools optimize resource utilizasion by abstracting thread management complexities, they present challenges in result retrieval, exception propagation, and coordinating dependent operations. TPL resolves these issues by providing a higher-level abstraction over thread pools with enhanced APIs for asynchronous operation composition.

The core concept centers around tasks, which represent asynchronous operations that may execute with or without dedicated threads. Tasks enable fine-grained control over parallel execution patterns while maintaining simplicity.

Creating Tasks Through Multiple Approaches

using System;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;
using static System.Threading.Thread;

class TaskCreationDemo
{
    static void Main()
    {
        // Direct task instantiation requires explicit start invocation
        var operation1 = new Task(() => ExecuteOperation("Operation 1"));
        var operation2 = new Task(() => ExecuteOperation("Operation 2"));
        operation1.Start();
        operation2.Start();
        
        // Task.Run provides convenient execution without manual start
        Task.Run(() => ExecuteOperation("Operation 3"));
        
        // Factory pattern offers additional configuration options
        Task.Factory.StartNew(() => ExecuteOperation("Operation 4"));
        
        // Long-running operations bypass thread pool optimization
        Task.Factory.StartNew(
            () => ExecuteOperation("Operation 5"), 
            TaskCreationOptions.LongRunning);
            
        Sleep(TimeSpan.FromSeconds(1));
    }
    
    static void ExecuteOperation(string identifier)
    {
        WriteLine($"{identifier} executing on thread {CurrentThread.ManagedThreadId}. " +
                 $"Pool thread: {CurrentThread.IsThreadPoolThread}");
    }
}

Retrieving Results from Asynchronous Operations

using System;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;
using static System.Threading.Thread;

class ResultRetrievalDemo
{
    static void Main()
    {
        // Synchronous execution context
        ProcessOperation("Main Operation");
        
        // Standard asynchronous result retrieval
        Task<int> asyncOp = CreateAsyncOperation("Async Op 1");
        asyncOp.Start();
        int outcome = asyncOp.Result;
        WriteLine($"Computed value: {outcome}");
        
        // Synchronous execution optimization
        asyncOp = CreateAsyncOperation("Sync Op 2");
        asyncOp.RunSynchronously();
        outcome = asyncOp.Result;
        WriteLine($"Computed value: {outcome}");
        
        // Non-blocking status monitoring
        asyncOp = CreateAsyncOperation("Monitoring Op 3");
        WriteLine(asyncOp.Status);
        asyncOp.Start();
        
        while (!asyncOp.IsCompleted)
        {
            WriteLine(asyncOp.Status);
            Sleep(TimeSpan.FromSeconds(0.5));
        }
        
        WriteLine(asyncOp.Status);
        outcome = asyncOp.Result;
        WriteLine($"Computed value: {outcome}");
    }
    
    static Task<int> CreateAsyncOperation(string label)
    {
        return new Task<int>(() => ProcessOperation(label));
    }
    
    static int ProcessOperation(string label)
    {
        WriteLine($"{label} running on thread {CurrentThread.ManagedThreadId}. " +
                 $"Pool thread: {CurrentThread.IsThreadPoolThread}");
        Sleep(TimeSpan.FromSeconds(2));
        return 42;
    }
}

Composing Dependent Task Chains

using System;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;
using static System.Threading.Thread;

class TaskCompositionDemo
{
    static void Main()
    {
        var initialTask = new Task<int>(() => PerformCalculation("Initial", 3));
        var secondaryTask = new Task<int>(() => PerformCalculation("Secondary", 2));
        
        // Sequential continuation with conditional execution
        initialTask.ContinueWith(
            t => WriteLine($"First result: {t.Result}. Thread {CurrentThread.ManagedThreadId}"),
            TaskContinuationOptions.OnlyOnRanToCompletion);
            
        initialTask.Start();
        secondaryTask.Start();
        Sleep(TimeSpan.FromSeconds(4));
        
        // Synchronous continuation optimization
        Task continuation = secondaryTask.ContinueWith(
            t => WriteLine($"Second result: {t.Result}. Thread {CurrentThread.ManagedThreadId}"),
            TaskContinuationOptions.OnlyOnRanToCompletion |
            TaskContinuationOptions.ExecuteSynchronously);
            
        // Nested continuation using modern awaiter pattern
        continuation.GetAwaiter().OnCompleted(
            () => WriteLine($"Chain completed! Thread {CurrentThread.ManagedThreadId}"));
            
        Sleep(TimeSpan.FromSeconds(2));
        
        // Parent-child task relationships
        initialTask = new Task<int>(() =>
        {
            var childTask = Task.Factory.StartNew(
                () => PerformCalculation("Child", 5),
                TaskCreationOptions.AttachedToParent);
                
            childTask.ContinueWith(
                t => PerformCalculation("Grandchild", 2),
                TaskContinuationOptions.AttachedToParent);
                
            return PerformCalculation("Parent", 2);
        });
        
        initialTask.Start();
        
        while (!initialTask.IsCompleted)
        {
            WriteLine(initialTask.Status);
            Sleep(TimeSpan.FromSeconds(0.5));
        }
        
        WriteLine(initialTask.Status);
        Sleep(TimeSpan.FromSeconds(10));
    }
    
    static int PerformCalculation(string name, int duration)
    {
        WriteLine($"{name} executing on thread {CurrentThread.ManagedThreadId}. " +
                 $"Pool thread: {CurrentThread.IsThreadPoolThread}");
        Sleep(TimeSpan.FromSeconds(duration));
        return 42 * duration;
    }
}

Converting Legacy APM Patterns to Task-Based Models

using System;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;
using static System.Threading.Thread;

class APMMigrationDemo
{
    delegate string AsyncProcessor(string parameter);
    delegate string IncompatibleProcessor(out int threadIdentifier);
    
    static void Main()
    {
        int threadId;
        AsyncProcessor processor = ExecuteProcessing;
        IncompatibleProcessor incompatible = ExecuteProcessing;
        
        // Method 1: Conversion with explicit callback handling
        WriteLine("Approach 1");
        Task<string> task = Task<string>.Factory.FromAsync(
            processor.BeginInvoke("AsyncThread", HandleCallback, "async invocation"),
            processor.EndInvoke);
            
        task.ContinueWith(t => WriteLine($"Callback complete. Result: {t.Result}"));
        
        while (!task.IsCompleted)
        {
            WriteLine(task.Status);
            Sleep(TimeSpan.FromSeconds(0.5));
        }
        
        WriteLine(task.Status);
        Sleep(TimeSpan.FromSeconds(1));
        
        WriteLine("----------------------------------------------");
        WriteLine("Approach 2");
        
        // Method 2: Simplified conversion without callbacks
        task = Task<string>.Factory.FromAsync(
            processor.BeginInvoke, processor.EndInvoke, 
            "AsyncThread", "async invocation");
            
        task.ContinueWith(t => WriteLine($"Task complete. Result: {t.Result}"));
        
        while (!task.IsCompleted)
        {
            WriteLine(task.Status);
            Sleep(TimeSpan.FromSeconds(0.5));
        }
        
        WriteLine(task.Status);
        Sleep(TimeSpan.FromSeconds(1));
        
        WriteLine("----------------------------------------------");
        WriteLine("Approach 3");
        
        // Method 3: Handling incompatible signatures
        IAsyncResult result = incompatible.BeginInvoke(
            out threadId, HandleCallback, "async invocation");
            
        task = Task<string>.Factory.FromAsync(
            result, _ => incompatible.EndInvoke(out threadId, result));
            
        task.ContinueWith(t =>
            WriteLine($"Task complete. Result: {t.Result}, Thread: {threadId}"));
            
        while (!task.IsCompleted)
        {
            WriteLine(task.Status);
            Sleep(TimeSpan.FromSeconds(0.5));
        }
        
        WriteLine(task.Status);
        Sleep(TimeSpan.FromSeconds(1));
    }
    
    static void HandleCallback(IAsyncResult asyncResult)
    {
        WriteLine("Callback initiated...");
        WriteLine($"State: {asyncResult.AsyncState}");
        WriteLine($"Pool thread: {CurrentThread.IsThreadPoolThread}");
        WriteLine($"Thread ID: {CurrentThread.ManagedThreadId}");
    }
    
    static string ExecuteProcessing(string param)
    {
        WriteLine("Processing initiated...");
        WriteLine($"Pool thread: {CurrentThread.IsThreadPoolThread}");
        Sleep(TimeSpan.FromSeconds(2));
        CurrentThread.Name = param;
        return $"Thread: {CurrentThread.Name}";
    }
    
    static string ExecuteProcessing(out int identifier)
    {
        WriteLine("Processing initiated with output...");
        WriteLine($"Pool thread: {CurrentThread.IsThreadPoolThread}");
        Sleep(TimeSpan.FromSeconds(2));
        identifier = CurrentThread.ManagedThreadId;
        return $"Thread ID: {identifier}";
    }
}

Migrating Event-Based Asynchronous Patterns

using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;
using static System.Threading.Thread;

class EAPMigrationDemo
{
    static void Main()
    {
        var completionSource = new TaskCompletionSource<int>();
        var backgroundWorker = new BackgroundWorker();
        
        backgroundWorker.DoWork += (sender, args) =>
        {
            args.Result = ExecuteBackgroundOperation("Background Worker", 5);
        };
        
        backgroundWorker.RunWorkerCompleted += (sender, args) =>
        {
            if (args.Error != null)
                completionSource.SetException(args.Error);
            else if (args.Cancelled)
                completionSource.SetCanceled();
            else
                completionSource.SetResult((int)args.Result);
        };
        
        backgroundWorker.RunWorkerAsync();
        int result = completionSource.Task.Result;
        WriteLine($"Final result: {result}");
    }
    
    static int ExecuteBackgroundOperation(string name, int seconds)
    {
        WriteLine($"{name} running on thread {CurrentThread.ManagedThreadId}. " +
                 $"Pool thread: {CurrentThread.IsThreadPoolThread}");
        Sleep(TimeSpan.FromSeconds(seconds));
        return 42 * seconds;
    }
}

Implementing Cancellation Mechanisms

using System;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;
using static System.Threading.Thread;

class CancellationDemo
{
    static void Main()
    {
        // Pre-execution cancellation
        var cancellationSource = new CancellationTokenSource();
        var longRunningTask = new Task<int>(
            () => ProcessWithCancellation("Task 1", 10, cancellationSource.Token), 
            cancellationSource.Token);
            
        WriteLine(longRunningTask.Status);
        cancellationSource.Cancel();
        WriteLine(longRunningTask.Status);
        WriteLine("Task cancelled before initiation");
        
        // Runtime cancellation
        cancellationSource = new CancellationTokenSource();
        longRunningTask = new Task<int>(
            () => ProcessWithCancellation("Task 2", 10, cancellationSource.Token), 
            cancellationSource.Token);
            
        longRunningTask.Start();
        
        for (int i = 0; i < 5; i++)
        {
            Sleep(TimeSpan.FromSeconds(0.5));
            WriteLine(longRunningTask.Status);
        }
        
        cancellationSource.Cancel();
        
        for (int i = 0; i < 5; i++)
        {
            Sleep(TimeSpan.FromSeconds(0.5));
            WriteLine(longRunningTask.Status);
        }
        
        WriteLine($"Task completed with result {longRunningTask.Result}.");
    }
    
    static int ProcessWithCancellation(string name, int duration, CancellationToken token)
    {
        WriteLine($"{name} executing on thread {CurrentThread.ManagedThreadId}. " +
                 $"Pool thread: {CurrentThread.IsThreadPoolThread}");
                 
        for (int i = 0; i < duration; i++)
        {
            Sleep(TimeSpan.FromSeconds(1));
            if (token.IsCancellationRequested) return -1;
        }
        
        return 42 * duration;
    }
}

Exception Handling in Asynchronous Contexts

using System;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;
using static System.Threading.Thread;

class ExceptionHandlingDemo
{
    static void Main()
    {
        Task<int> asyncTask;
        
        // Standard exception wrapping
        try
        {
            asyncTask = Task.Run(() => ExecuteWithError("Task 1", 2));
            int result = asyncTask.Result;
            WriteLine($"Result: {result}");
        }
        catch (Exception ex)
        {
            WriteLine($"Exception captured: {ex}");
        }
        
        WriteLine("----------------------------------------------");
        
        // Direct exception extraction
        try
        {
            asyncTask = Task.Run(() => ExecuteWithError("Task 2", 2));
            int result = asyncTask.GetAwaiter().GetResult();
            WriteLine($"Result: {result}");
        }
        catch (Exception ex)
        {
            WriteLine($"Exception captured: {ex}");
        }
        
        WriteLine("----------------------------------------------");
        
        // Multiple exception aggregation
        var task1 = new Task<int>(() => ExecuteWithError("Task 3", 3));
        var task2 = new Task<int>(() => ExecuteWithError("Task 4", 2));
        var combinedTask = Task.WhenAll(task1, task2);
        
        var errorHandler = combinedTask.ContinueWith(t =>
            WriteLine($"Exceptions: {t.Exception}"),
            TaskContinuationOptions.OnlyOnFaulted);
            
        task1.Start();
        task2.Start();
        Sleep(TimeSpan.FromSeconds(5));
    }
    
    static int ExecuteWithError(string name, int seconds)
    {
        WriteLine($"{name} running on thread {CurrentThread.ManagedThreadId}. " +
                 $"Pool thread: {CurrentThread.IsThreadPoolThread}");
        Sleep(TimeSpan.FromSeconds(seconds));
        throw new Exception("Failure occurred!");
        return 42 * seconds;
    }
}

Concurrent Task Execution Strategies

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;
using static System.Threading.Thread;

class ConcurrentExecutionDemo
{
    static void Main()
    {
        var firstOperation = new Task<int>(() => ProcessConcurrent("First", 3));
        var secondOperation = new Task<int>(() => ProcessConcurrent("Second", 2));
        var allCompleted = Task.WhenAll(firstOperation, secondOperation);
        
        allCompleted.ContinueWith(t =>
            WriteLine($"Results - First: {t.Result[0]}, Second: {t.Result[1]}"),
            TaskContinuationOptions.OnlyOnRanToCompletion);
            
        firstOperation.Start();
        secondOperation.Start();
        Sleep(TimeSpan.FromSeconds(4));
        
        var operationList = new List<Task<int>>();
        
        for (int i = 1; i < 4; i++)
        {
            int counter = i;
            var task = new Task<int>(() => ProcessConcurrent($"Task {counter}", counter));
            operationList.Add(task);
            task.Start();
        }
        
        while (operationList.Count > 0)
        {
            var completed = Task.WhenAny(operationList).Result;
            operationList.Remove(completed);
            WriteLine($"Task completed with result {completed.Result}.");
        }
        
        Sleep(TimeSpan.FromSeconds(1));
    }
    
    static int ProcessConcurrent(string name, int seconds)
    {
        WriteLine($"{name} executing on thread {CurrentThread.ManagedThreadId}. " +
                 $"Pool thread: {CurrentThread.IsThreadPoolThread}");
        Sleep(TimeSpan.FromSeconds(seconds));
        return 42 * seconds;
    }
}

Configuring Task Execution with Custom Schedulers

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

class UISchedulerDemo : Window
{
    private System.Windows.Controls.TextBlock ContentTextBlock;
    private System.Windows.Controls.Button syncButton;
    private System.Windows.Controls.Button asyncButton;
    private System.Windows.Controls.Button optimizedButton;
    
    void SyncButtonClick(object sender, RoutedEventArgs e)
    {
        ContentTextBlock.Text = string.Empty;
        try
        {
            string result = ProcessUITask().Result;
            ContentTextBlock.Text = result;
        }
        catch (Exception ex)
        {
            ContentTextBlock.Text = ex.InnerException.Message;
        }
    }
    
    void AsyncButtonClick(object sender, RoutedEventArgs e)
    {
        ContentTextBlock.Text = string.Empty;
        Mouse.OverrideCursor = Cursors.Wait;
        Task<string> task = ProcessUITask();
        
        task.ContinueWith(t => 
            {
                ContentTextBlock.Text = t.Exception.InnerException.Message;
                Mouse.OverrideCursor = null;
            }, 
            CancellationToken.None,
            TaskContinuationOptions.OnlyOnFaulted,
            TaskScheduler.FromCurrentSynchronizationContext());
    }
    
    void OptimizedButtonClick(object sender, RoutedEventArgs e)
    {
        ContentTextBlock.Text = string.Empty;
        Mouse.OverrideCursor = Cursors.Wait;
        Task<string> task = ProcessUITask(TaskScheduler.FromCurrentSynchronizationContext());
        
        task.ContinueWith(t => Mouse.OverrideCursor = null,
            CancellationToken.None,
            TaskContinuationOptions.None,
            TaskScheduler.FromCurrentSynchronizationContext());
    }
    
    Task<string> ProcessUITask()
    {
        return ProcessUITask(TaskScheduler.Default);
    }
    
    Task<string> ProcessUITask(TaskScheduler scheduler)
    {
        Task delay = Task.Delay(TimeSpan.FromSeconds(5));
        
        return delay.ContinueWith(t =>
        {
            string message =
                "Task executing on thread " +
                $"{CurrentThread.ManagedThreadId}. Pool thread: " +
                $"{CurrentThread.IsThreadPoolThread}";
                
            ContentTextBlock.Text = message;
            return message;
        }, scheduler);
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.