Mastering Task-Based Asynchronous Programming in C#
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);
}
}