TaskListProcessor

Mastering Concurrent Operations in .NET

Handle multiple asynchronous operations with grace and precision. Build scalable and maintainable .NET applications with advanced error handling and telemetry.

Source Code Available

The source code for TaskListProcessor is available on GitHub . Clone the repository to follow along with the examples.

In application development, efficiency is not just a goal; it's a necessity. As .NET developers, we often encounter scenarios where we must juggle multiple operations simultaneously. The TaskListProcessor class addresses these issues with efficiency, handling multiple asynchronous operations with grace and precision.

Development Challenges

The TaskListProcessor addresses common challenges in .NET concurrent programming, providing a structured approach to running concurrent tasks with different return types.

Issue: Diverse Return Types

A common issue with concurrent async methods in .NET is handling different return types. Traditional approaches often lead to tangled code, where task management becomes cumbersome and error-prone.

Issue: Error Propagation

Without proper structure, errors from individual tasks can propagate and cause widespread failures. This necessitates a robust mechanism to encapsulate errors and handle them gracefully.

Solution: TaskListProcessor

The TaskListProcessor class addresses these issues head-on, providing a cohesive way to manage a list of tasks regardless of their return types, with built-in error handling and logging.

Benefit: Enhanced Performance

Features methods like WhenAllWithLoggingAsync, which enhance the standard Task.WhenAll with error oversight, flexibility, and scalability using generics and TaskResult objects.

Task.WhenAll vs. Parallel Methods

Task.WhenAll vs Parallel.ForEach comparison

The choice between using Task.WhenAll and Parallel methods can be effectively illustrated through the metaphor of runners on a track and a tug-of-war contest.

Understanding these differences will help us appreciate the benefits of the TaskListProcessor and select the right approach for your concurrent processing needs.

Task.WhenAll as Runners

Imagine runners, each in their own lane on a track. This represents Task.WhenAll for handling asynchronous, I/O-bound tasks. Each task runs independently without blocking others, ensuring efficiency for network requests or file I/O operations.

Parallel Methods as Tug-of-War

A tug-of-war contest represents Parallel methods for CPU-bound tasks. Teams work together with synchronized effort, similar to how parallel processing distributes computational weight across multiple threads for maximum CPU utilization.

Use Case: Travel Website Dashboard

Task List Processor Use Case for travel website

Consider a travel website displaying a dashboard of top destination cities, aggregating data like weather, attractions, events, and flights from multiple sources.

Performance Challenges
  • Diverse data sources with different response times
  • Inconsistent data retrieval complexity
TaskListProcessor Solution
  • Concurrent data retrieval for faster loading
  • Robust error handling prevents cascade failures
Business Benefits
  • Responsive dashboard with partial data display
  • Maintains user engagement and trust

Technical Jargon Explained

Understanding these technical concepts will help you grasp how TaskListProcessor enhances concurrent task management in .NET applications.

Concurrent Asynchronous Tasks

Tasks executed simultaneously, each operating independently and completing at its own pace. Essential in multi-threading environments for optimal performance.

Error Propagation

The process where errors spread from one system part to others, potentially causing wider failures. Effective error handling prevents this cascade effect.

Telemetry

Collection and analysis of system performance data, including metrics like task execution time and error rates for monitoring and optimization.

ILogger & TaskResult

ILogger: .NET interface for logging events. TaskResult: Custom object encapsulating task outcomes, storing results, names, and error details.

The WhenAllWithLoggingAsync Method

The WhenAllWithLoggingAsync method enhances the standard Task.WhenAll with robust error handling and centralized logging capabilities.

public static async Task WhenAllWithLoggingAsync(IEnumerable<Task> tasks, ILogger logger)
{
  ArgumentNullException.ThrowIfNull(logger);
  try
  {
    await Task.WhenAll(tasks);
  }
  catch (Exception ex)
  {
    logger.LogError(ex, "TLP: An error occurred while executing one or more tasks.");
  }
}
Enhanced Error Handling

Instead of allowing exceptions to propagate and potentially crash the application, it catches exceptions and logs them for debugging and analysis.

Consolidated Logging

Centralized logging of task exceptions with consistent formatting, essential for integrating with logging solutions and services.

Non-Blocking Operation

Logs errors internally and allows program continuation, beneficial for non-critical tasks that shouldn't block overall process.

Improved Maintenance

Detailed error information aids in faster debugging and simplifies maintenance in complex systems with many concurrent tasks.

The GetTaskResultAsync Method

The GetTaskResultAsync method wraps async calls with telemetry features, measuring execution time and providing performance metrics.

public async Task GetTaskResultAsync<T>(string taskName, Task<T> task) where T : class
{
  var sw = new Stopwatch();
  sw.Start();
  var taskResult = new TaskResult { Name = taskName };
  try
  {
    taskResult.Data = await task;
    sw.Stop();
    Telemetry.Add(GetTelemetry(taskName, sw.ElapsedMilliseconds));
  }
  catch (Exception ex)
  {
    sw.Stop();
    Telemetry.Add(GetTelemetry(taskName, sw.ElapsedMilliseconds, "Exception", ex.Message));
    taskResult.Data = null;
  }
  finally
  {
    TaskResults.Add(taskResult);
  }
}
Performance Metrics

Utilizes Stopwatch to measure and record task execution time, providing valuable performance insights for optimization.

Error Tracking

Captures exceptions during task execution, logging errors with task names and elapsed time for comprehensive analysis.

Execution Isolation

Each task executes in a separate logical block, allowing independent handling where one task failure doesn't impede others.

Generic Flexibility

Designed to return various object types from different tasks within a single list, enabling heterogeneous task processing.

TaskResult Class Overview

The TaskResult class is a cornerstone within the TaskListProcessor architecture, designed to encapsulate the outcome of asynchronous tasks with unified structure.

public class TaskResult<T> : ITaskResult
{
  public TaskResult()
  {
    Name = "UNKNOWN";
    Data = null;
  }

  public TaskResult(string name, T data)
  {
    Name = name;
    Data = data;
  }
  public T? Data { get; set; }
  public string Name { get; set; }
}
Purpose & Standardization

Offers a standardized object representing any task outcome, regardless of nature or return data type, promoting consistency across applications.

Generic Flexibility

Thanks to generic design, can hold any result data type, making it versatile across projects and scenarios for capturing task execution results.

Error Handling

When tasks fail, stores error details alongside original task information, making it invaluable for error tracking and debugging processes.

Telemetry Integration

Can be extended to include telemetry data like execution duration, crucial for performance monitoring and optimization in complex systems.

WeatherService Implementation

The WeatherService class simulates real-world external service calls with artificial latency and potential failures for comprehensive testing scenarios.

Real-World Simulation

Mimics external service calls by introducing artificial latency using Random class, simulating network or service delays developers encounter in production.

Randomized Error Injection

Deliberately throws exceptions based on random conditions to simulate real service failures, ensuring applications handle intermittent failures gracefully.

Adjustable Failure Rate

Customizable failure likelihood provides flexibility in error introduction frequency, allowing thorough testing under various stress and instability conditions.

Realistic Testing

Incorporates randomness in latency and failures for realistic testing environments, ensuring applications handle both success scenarios and unexpected delays.

Travel Dashboard Demo

Task List Processor Dashboard demonstration

Let's examine a practical implementation that demonstrates fetching weather forecasts for multiple cities concurrently, simulating a real-world travel dashboard.

Concurrent Data Retrieval

Asynchronous programming makes non-blocking calls to WeatherService for each city, demonstrating parallel data loading for reduced total wait time.

Console Logging Demo

Console.WriteLine outputs progress and results, providing clear sequential logs that mimic how users see data loading on web dashboards.

var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = loggerFactory.CreateLogger<Program>();
var weatherService = new WeatherService();
var weatherCities = new TaskListProcessing.TaskListProcessor();
var cities = new List<string> { "London", "Paris", "New York", "Tokyo", "Sydney", "Chicago", "Dallas", "Wichita" };
var tasks = new List<Task>();

foreach (var city in cities)
{
  tasks.Add(weatherCities.GetTaskResultAsync(city, weatherService.GetWeather(city)));
}

await TaskListProcessing.TaskListProcessor.WhenAllWithLoggingAsync(tasks, logger);
Sample Output
Telemetry:
Chicago: Task completed in 602 ms with ERROR Exception: Random failure occurred fetching weather data.
Paris: Task completed in 723 ms with ERROR Exception: Random failure occurred fetching weather data.
Dallas: Task completed in 1,009 ms
Sydney: Task completed in 1,318 ms
Tokyo: Task completed in 1,921 ms
London: Task completed in 2,789 ms

Results:
Dallas: City: Dallas, Date: 2023-11-10, Temp (F): 40, Summary: Cool
Sydney: City: Sydney, Date: 2023-11-10, Temp (F): 116, Summary: Sweltering
Tokyo: City: Tokyo, Date: 2023-11-10, Temp (F): 75, Summary: Warm
London: City: London, Date: 2023-11-10, Temp (F): 16, Summary: Chilly

Enhanced Implementation: CityThingsToDo Service

Updated Task List Processor Dashboard with activities

The latest enhancement introduces the CityThingsToDo service, showcasing the power and flexibility of our architecture to seamlessly integrate different service types and return types.

Architecture Benefits

This enhancement demonstrates how TaskListProcessor handles diverse data sources, reflecting real-world scenarios like travel dashboards that provide weather forecasts and activities for different cities with uniform error handling and telemetry.

var thingsToDoService = new CityThingsToDoService();
var weatherService = new WeatherService();
var cityDashboards = new TaskListProcessorGeneric();
var cities = new List<string> { "London", "Paris", "New York", "Tokyo", "Sydney", "Chicago", "Dallas", "Wichita" };
var tasks = new List<Task>();

foreach (var city in cities)
{
  tasks.Add(cityDashboards.GetTaskResultAsync($"{city} Weather", weatherService.GetWeather(city)));
  tasks.Add(cityDashboards.GetTaskResultAsync($"{city} Things To Do", thingsToDoService.GetThingsToDoAsync(city)));
}

await cityDashboards.WhenAllWithLoggingAsync(tasks, logger);

Take TaskListProcessor for a Test Drive

In the realm of .NET, orchestrating concurrent asynchronous tasks is a common challenge with the potential to become a transformative learning journey.

Test Drive Experience

Clone the repository and experience the power of TaskListProcessor firsthand. A practical step in the lifelong learning journey for .NET developers.

View on GitHub
Learning Outcomes

Master concurrent operations, understand error handling strategies, and implement performance monitoring in your .NET applications.

Version Compatibility

TaskListProcessor is developed for .NET 9, offering the latest framework features and optimizations. Regular updates ensure compatibility with new .NET releases and performance enhancements.

Follow our GitHub repository for the latest updates and access to the most current version with ongoing improvements and community contributions.