A modern, enterprise-grade .NET 9.0 library for orchestrating asynchronous operations with comprehensive telemetry, circuit breakers, dependency injection, and advanced scheduling capabilities.
- π TaskListProcessor
- π Table of Contents
- β¨ Overview
- π― Why TaskListProcessor?
- π₯ Key Features
- ποΈ Architecture
- β‘ Quick Start
- π Comprehensive Examples
- π§ Advanced Usage
- π Performance & Telemetry
- π οΈ API Reference
- π§ͺ Testing
- π Migration Guide
- ποΈ Project Structure
- π€ Contributing
- π License
- π Resources
TaskListProcessor is a production-ready .NET 9.0 library designed to solve complex asynchronous orchestration challenges in modern applications. Built with enterprise-grade patterns including dependency injection, circuit breakers, task scheduling, and comprehensive telemetry, it provides a robust foundation for high-throughput, fault-tolerant systems.
The Problem: Modern applications require sophisticated coordination of multiple async operationsβAPI calls, database queries, file I/O, microservice interactionsβwhile maintaining resilience, observability, and performance under varying loads.
The Solution: TaskListProcessor provides a battle-tested, enterprise-ready framework with:
- π‘οΈ Fault Isolation: Circuit breakers and individual task failure isolation
- π Enterprise Observability: OpenTelemetry integration with rich metrics and tracing
- β‘ Advanced Scheduling: Priority-based, dependency-aware task execution
- π― Type Safety: Strongly-typed results with comprehensive error categorization
- π§ Dependency Injection: Native .NET DI integration with decorator pattern support
- ποΈ Interface Segregation: Clean, focused interfaces following SOLID principles
- π Concurrent Execution: Parallel task processing with configurable concurrency limits and load balancing
- π‘οΈ Circuit Breaker Pattern: Automatic failure detection and cascading failure prevention
- π Rich Telemetry: Comprehensive timing, success rates, error tracking, and OpenTelemetry integration
- π― Type Safety: Strongly-typed results with full IntelliSense support and error categorization
- β±οΈ Timeout & Cancellation: Built-in support for graceful shutdown and per-task timeouts
- π Task Dependencies: Dependency resolution with topological sorting and execution ordering
- ποΈ Dependency Injection: Native .NET DI integration with fluent configuration API
- π¨ Interface Segregation: Clean, focused interfaces following SOLID principles
- οΏ½ Decorator Pattern: Pluggable cross-cutting concerns (logging, metrics, circuit breakers)
- π Advanced Scheduling: Priority-based, FIFO, LIFO, and custom scheduling strategies
- π§΅ Thread Safety: Lock-free concurrent collections and thread-safe operations
- πΎ Memory Optimization: Object pooling and efficient memory management
- οΏ½ Structured Logging: Integration with Microsoft.Extensions.Logging and Serilog
- π Health Checks: Built-in health monitoring and diagnostic capabilities
- οΏ½ Streaming Results: Async enumerable support for real-time result processing
- π§ͺ Testing Support: Comprehensive test helpers and mock-friendly interfaces
- π Rich Documentation: Extensive XML documentation and practical examples
TaskListProcessor implements a modern, enterprise-ready architecture with clear separation of concerns:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Dependency Injection Layer β
β services.AddTaskListProcessor().WithAllDecorators() β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Decorator Chain β
β LoggingDecorator β MetricsDecorator β CircuitBreakerDecorator β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Interface Segregation Layer β
β ITaskProcessor β ITaskBatchProcessor β ITaskStreamProcessor β
β ITaskTelemetryProvider β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Core Processing Engine β
β TaskListProcessorEnhanced (Backward Compatible) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββ
β β β
βββββββββββΌβββββββββ βββββββΌβββββββ βββββββββΌβββββββ
β TaskDefinition β βTaskTelemetryβ βTaskProgress β
β + Dependencies β β + Metrics β β + Reporting β
β + Priority β β + Tracing β β + Streaming β
β + Scheduling β β + Health β β + Estimates β
ββββββββββββββββββββ ββββββββββββββ ββββββββββββββββ
- Interface Layer: Clean, focused interfaces for different processing scenarios
- Decorator Layer: Cross-cutting concerns (logging, metrics, circuit breakers)
- Processing Engine: Thread-safe orchestration with advanced scheduling
- Telemetry System: Comprehensive observability and health monitoring
- Dependency Resolution: Topological sorting and execution ordering
- Circuit Breaker: Cascading failure prevention and automatic recovery
# Clone the repository
git clone https://github.com/markhazleton/TaskListProcessor.git
cd TaskListProcessor
# Build the solution
dotnet build
# Run the demo
dotnet run --project examples/TaskListProcessor.Console
using TaskListProcessing.Core;
using Microsoft.Extensions.Logging;
// Set up logging (optional but recommended)
using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = loggerFactory.CreateLogger<Program>();
// Create the processor
using var processor = new TaskListProcessorEnhanced("My Tasks", logger);
// Define your tasks using the factory pattern
var taskFactories = new Dictionary<string, Func<CancellationToken, Task<object?>>>
{
["Weather Data"] = async ct => await GetWeatherAsync("London"),
["Stock Prices"] = async ct => await GetStockPricesAsync("MSFT"),
["User Data"] = async ct => await GetUserDataAsync(userId)
};
// Execute all tasks concurrently
await processor.ProcessTasksAsync(taskFactories, cancellationToken);
// Access results and telemetry
foreach (var result in processor.TaskResults)
{
Console.WriteLine($"{result.Name}: {(result.IsSuccessful ? "β
" : "β")}");
}
using TaskListProcessing.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
// Program.cs
var builder = Host.CreateApplicationBuilder(args);
// Configure TaskListProcessor with decorators
builder.Services.AddTaskListProcessor(options =>
{
options.MaxConcurrentTasks = 10;
options.EnableDetailedTelemetry = true;
options.CircuitBreakerOptions = new() { FailureThreshold = 3 };
})
.WithLogging()
.WithMetrics()
.WithCircuitBreaker();
var host = builder.Build();
// Usage in your services
public class MyService
{
private readonly ITaskBatchProcessor _processor;
public MyService(ITaskBatchProcessor processor)
{
_processor = processor;
}
public async Task ProcessDataAsync()
{
var tasks = new Dictionary<string, Func<CancellationToken, Task<object?>>>
{
["API Call"] = async ct => await CallApiAsync(ct),
["DB Query"] = async ct => await QueryDatabaseAsync(ct)
};
await _processor.ProcessTasksAsync(tasks);
}
}
This example demonstrates fetching weather and activities data for multiple cities:
using var processor = new TaskListProcessorEnhanced("Travel Dashboard", logger);
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2));
var cities = new[] { "London", "Paris", "Tokyo", "New York" };
var taskFactories = new Dictionary<string, Func<CancellationToken, Task<object?>>>();
// Create tasks for each city
foreach (var city in cities)
{
taskFactories[$"{city} Weather"] = ct => weatherService.GetWeatherAsync(city, ct);
taskFactories[$"{city} Activities"] = ct => activitiesService.GetActivitiesAsync(city, ct);
}
// Execute and handle results
try
{
await processor.ProcessTasksAsync(taskFactories, cts.Token);
// Group results by city
var cityData = processor.TaskResults
.GroupBy(r => r.Name.Split(' ')[0])
.ToDictionary(g => g.Key, g => g.ToList());
// Display results with rich formatting
foreach (var (city, results) in cityData)
{
Console.WriteLine($"\nπ {city}:");
foreach (var result in results)
{
var status = result.IsSuccessful ? "β
" : "β";
Console.WriteLine($" {status} {result.Name.Split(' ')[1]}");
}
}
}
catch (OperationCanceledException)
{
logger.LogWarning("Operation timed out after 2 minutes");
}
For scenarios requiring fine-grained control:
// Execute a single task with full telemetry
var result = await processor.ExecuteTaskAsync(
"Critical API Call",
httpClient.GetStringAsync("https://api.example.com/data"),
cancellationToken);
if (result.IsSuccessful)
{
var data = result.Data;
logger.LogInformation("Received {DataLength} characters", data?.Length ?? 0);
}
else
{
logger.LogError("API call failed: {Error}", result.ErrorMessage);
}
using TaskListProcessing.Models;
using TaskListProcessing.Scheduling;
// Configure with dependency resolution
var options = new TaskListProcessorOptions
{
DependencyResolver = new TopologicalTaskDependencyResolver(),
SchedulingStrategy = TaskSchedulingStrategy.Priority,
MaxConcurrentTasks = Environment.ProcessorCount * 2
};
using var processor = new TaskListProcessorEnhanced("Advanced Tasks", logger, options);
// Define tasks with dependencies and priorities
var taskDefinitions = new[]
{
new TaskDefinition
{
Name = "Initialize",
Factory = async ct => await InitializeAsync(ct),
Priority = TaskPriority.High
},
new TaskDefinition
{
Name = "Process Data",
Factory = async ct => await ProcessDataAsync(ct),
Dependencies = new[] { "Initialize" },
Priority = TaskPriority.Medium
},
new TaskDefinition
{
Name = "Generate Report",
Factory = async ct => await GenerateReportAsync(ct),
Dependencies = new[] { "Process Data" },
Priority = TaskPriority.Low
}
};
await processor.ProcessTaskDefinitionsAsync(taskDefinitions);
var options = new TaskListProcessorOptions
{
CircuitBreakerOptions = new CircuitBreakerOptions
{
FailureThreshold = 5,
RecoveryTimeout = TimeSpan.FromMinutes(2),
MinimumThroughput = 10
}
};
using var processor = new TaskListProcessorEnhanced("Resilient Tasks", logger, options);
// Tasks will automatically trigger circuit breaker on repeated failures
var taskFactories = new Dictionary<string, Func<CancellationToken, Task<object?>>>
{
["Resilient API"] = async ct => await CallExternalApiAsync(ct),
["Fallback Service"] = async ct => await CallFallbackServiceAsync(ct)
};
await processor.ProcessTasksAsync(taskFactories);
// Check circuit breaker status
var cbStats = processor.CircuitBreakerStats;
if (cbStats?.State == CircuitBreakerState.Open)
{
Console.WriteLine($"Circuit breaker opened at {cbStats.OpenedAt}");
}
using TaskListProcessing.Interfaces;
// Inject the stream processor
public class StreamingService
{
private readonly ITaskStreamProcessor _streamProcessor;
public StreamingService(ITaskStreamProcessor streamProcessor)
{
_streamProcessor = streamProcessor;
}
public async Task ProcessWithStreamingAsync()
{
var tasks = CreateLongRunningTasks();
// Process results as they complete
await foreach (var result in _streamProcessor.ProcessTasksStreamAsync(tasks))
{
Console.WriteLine($"Completed: {result.Name} - {result.IsSuccessful}");
// Process result immediately without waiting for all tasks
await HandleResultAsync(result);
}
}
}
var options = new TaskListProcessorOptions
{
HealthCheckOptions = new HealthCheckOptions
{
MinSuccessRate = 0.8, // 80% success rate threshold
MaxAverageExecutionTime = TimeSpan.FromSeconds(5),
IncludeCircuitBreakerState = true
}
};
using var processor = new TaskListProcessorEnhanced("Health Monitored", logger, options);
// After processing tasks
var healthResult = processor.PerformHealthCheck();
if (!healthResult.IsHealthy)
{
Console.WriteLine($"Health check failed: {healthResult.Message}");
}
// Get detailed telemetry
var telemetrySummary = processor.GetTelemetrySummary();
Console.WriteLine($"Success rate: {telemetrySummary.SuccessRate:F1}%");
Console.WriteLine($"Average execution time: {telemetrySummary.AverageExecutionTime:F0}ms");
TaskListProcessor provides comprehensive telemetry out of the box:
// Access telemetry after execution
var telemetry = processor.Telemetry;
var successRate = telemetry.Count(t => t.IsSuccessful) / (double)telemetry.Count * 100;
var averageTime = telemetry.Average(t => t.DurationMs);
var throughput = telemetry.Count / telemetry.Max(t => t.DurationMs) * 1000;
Console.WriteLine($"π Success Rate: {successRate:F1}%");
Console.WriteLine($"β±οΈ Average Time: {averageTime:F0}ms");
Console.WriteLine($"π Throughput: {throughput:F1} tasks/second");
=== π TELEMETRY SUMMARY ===
π Total Tasks: 16
β
Successful: 13 (81.2%)
β Failed: 3
β±οΈ Average Time: 1,305ms
π Fastest: 157ms | π Slowest: 2,841ms
β° Total Execution Time: 20,884ms
=== π DETAILED TELEMETRY ===
β
Successful Tasks (sorted by execution time):
π London Things To Do: 157ms
π Dallas Things To Do: 339ms
β‘ Chicago Things To Do: 557ms
π London Weather: 1,242ms
...
β Failed Tasks:
π₯ Sydney Things To Do: ArgumentException after 807ms
π₯ Tokyo Things To Do: ArgumentException after 424ms
Interface | Description | Key Methods |
---|---|---|
ITaskProcessor |
Single task execution | ExecuteTaskAsync<T>() |
ITaskBatchProcessor |
Batch processing | ProcessTasksAsync() , ProcessTaskDefinitionsAsync() |
ITaskStreamProcessor |
Streaming results | ProcessTasksStreamAsync() |
ITaskTelemetryProvider |
Telemetry & health | GetTelemetrySummary() , PerformHealthCheck() |
Method | Description | Returns |
---|---|---|
ProcessTasksAsync(taskFactories, progress, ct) |
Execute multiple tasks concurrently | Task |
ProcessTaskDefinitionsAsync(definitions, progress, ct) |
Execute tasks with dependencies | Task |
ExecuteTaskAsync<T>(name, task, ct) |
Execute single task with telemetry | Task<EnhancedTaskResult<T>> |
ProcessTasksStreamAsync(taskFactories, ct) |
Stream results as they complete | IAsyncEnumerable<EnhancedTaskResult<object>> |
GetTelemetrySummary() |
Get comprehensive telemetry | TelemetrySummary |
PerformHealthCheck() |
Check processor health | HealthCheckResult |
Option | Type | Default | Description |
---|---|---|---|
MaxConcurrentTasks |
int |
Environment.ProcessorCount * 2 |
Maximum concurrent tasks |
DefaultTaskTimeout |
TimeSpan |
5 minutes |
Default task timeout |
EnableDetailedTelemetry |
bool |
true |
Enable comprehensive telemetry |
CircuitBreakerOptions |
CircuitBreakerOptions? |
null |
Circuit breaker configuration |
SchedulingStrategy |
TaskSchedulingStrategy |
FirstInFirstOut |
Task scheduling strategy |
DependencyResolver |
ITaskDependencyResolver? |
null |
Dependency resolution |
Model | Description | Key Properties |
---|---|---|
EnhancedTaskResult<T> |
Task execution result | Data , IsSuccessful , ErrorMessage , ErrorCategory |
TaskTelemetry |
Telemetry data | TaskName , ElapsedMilliseconds , IsSuccessful |
TaskProgress |
Progress information | CompletedTasks , TotalTasks , CurrentTask |
TaskDefinition |
Task with metadata | Name , Factory , Dependencies , Priority |
CircuitBreakerStats |
Circuit breaker state | State , FailureCount , OpenedAt |
TelemetrySummary |
Aggregated telemetry | SuccessRate , AverageExecutionTime , TotalTasks |
Run the comprehensive test suite:
# Run all tests
dotnet test
# Run with coverage
dotnet test --collect:"XPlat Code Coverage"
# Run specific test category
dotnet test --filter Category=Integration
- β Unit Tests: Core functionality and edge cases
- β Integration Tests: End-to-end scenarios
- β Performance Tests: Throughput and latency validation
- β Stress Tests: High-concurrency scenarios
TaskListProcessor follows a clean architecture with clear separation of concerns:
TaskListProcessor/
βββ src/
β βββ TaskListProcessing/ # Core library
β β βββ Core/ # Core implementations
β β β βββ TaskListProcessorEnhanced.cs # Main orchestrator
β β β βββ TaskProcessor.cs # Single task execution
β β β βββ TaskBatchProcessor.cs # Batch processing
β β β βββ TaskStreamProcessor.cs # Streaming results
β β β βββ TaskTelemetryProvider.cs # Telemetry collection
β β βββ Interfaces/ # Interface segregation
β β β βββ ITaskProcessor.cs # Single task interface
β β β βββ ITaskBatchProcessor.cs # Batch processing interface
β β β βββ ITaskStreamProcessor.cs # Streaming interface
β β β βββ ITaskTelemetryProvider.cs # Telemetry interface
β β βββ Extensions/ # DI integration
β β β βββ ServiceCollectionExtensions.cs
β β β βββ TaskProcessorBuilder.cs
β β βββ Models/ # Data models
β β β βββ EnhancedTaskResult.cs
β β β βββ TaskDefinition.cs
β β β βββ TaskProgress.cs
β β β βββ HealthCheckResult.cs
β β βββ Scheduling/ # Task scheduling
β β β βββ TaskSchedulingStrategy.cs
β β β βββ AdvancedTaskScheduler.cs
β β β βββ TopologicalTaskDependencyResolver.cs
β β βββ CircuitBreaker/ # Circuit breaker pattern
β β β βββ CircuitBreaker.cs
β β β βββ CircuitBreakerOptions.cs
β β βββ LoadBalancing/ # Load balancing
β β β βββ LoadBalancingStrategy.cs
β β β βββ LoadBalancingTaskDistributor.cs
β β βββ Telemetry/ # Telemetry & metrics
β β β βββ TaskTelemetry.cs
β β β βββ TelemetrySummary.cs
β β β βββ SchedulerStats.cs
β β βββ Options/ # Configuration
β β β βββ TaskListProcessorOptions.cs
β β β βββ CircuitBreakerOptions.cs
β β β βββ HealthCheckOptions.cs
β β βββ Decorators/ # Cross-cutting concerns
β β β βββ LoggingTaskProcessorDecorator.cs
β β β βββ MetricsTaskProcessorDecorator.cs
β β βββ Testing/ # Test utilities
β β β βββ TaskListProcessorTestHelpers.cs
β β βββ Utilities/ # Helper classes
β βββ CityWeatherService/ # Example service
β β βββ WeatherService.cs
β β βββ CityWeatherService.csproj
β βββ CityThingsToDo/ # Example service
β βββ CityThingsToDoService.cs
β βββ CityThingsToDo.csproj
βββ examples/
β βββ TaskListProcessor.Console/ # Demo application
β βββ Program.cs
β βββ Utilities/
β βββ AppConfiguration.cs
β βββ OutputFormatter.cs
β βββ ResultsDisplay.cs
β βββ TelemetryDisplay.cs
βββ tests/
β βββ TaskListProcessing.Tests/ # Core library tests
β β βββ InterfaceSegregationTests.cs
β β βββ TaskListProcessing.Tests.csproj
β βββ CityWeatherService.Tests/ # Service tests
β β βββ WeatherServiceTests.cs
β β βββ CityWeatherService.Tests.csproj
β βββ CityThingsToDo.Tests/ # Service tests
β βββ CityThingsToDoServiceTests.cs
β βββ CityThingsToDo.Tests.csproj
βββ docs/ # Documentation
β βββ PHASE1_README.md # Phase 1 features
β βββ MIGRATION_GUIDE.md # Migration guide
β βββ CLEANUP_SUMMARY.md # Cleanup notes
βββ README.md # This file
- Interface Segregation: Clean, focused interfaces for different scenarios
- Dependency Injection: Native .NET DI with fluent configuration
- Single Responsibility: Each component has a clear, focused purpose
- Extensibility: Decorator pattern for cross-cutting concerns
- Testability: Mockable interfaces and comprehensive test coverage
Recommended Migration Path:
- Migrate to Dependency Injection (Recommended)
// Old approach
var processor = new TaskListProcessorEnhanced("Tasks", logger);
// New approach
services.AddTaskListProcessor(options =>
{
options.MaxConcurrentTasks = 10;
options.EnableDetailedTelemetry = true;
})
.WithLogging()
.WithMetrics();
// In your service
public class MyService
{
private readonly ITaskBatchProcessor _processor;
public MyService(ITaskBatchProcessor processor) => _processor = processor;
}
- Direct Interface Usage (Alternative)
// Single task processing
var taskProcessor = new TaskProcessor("SingleTasks", logger);
var result = await taskProcessor.ExecuteTaskAsync("task", someTask);
// Batch processing
var batchProcessor = new TaskBatchProcessor("BatchTasks", logger);
await batchProcessor.ProcessTasksAsync(taskFactories);
// Streaming results
var streamProcessor = new TaskStreamProcessor("StreamTasks", logger);
await foreach (var result in streamProcessor.ProcessTasksStreamAsync(tasks))
{
// Process results as they complete
}
- Backward Compatibility (For existing code)
// TaskListProcessorEnhanced still works with all existing features
using var processor = new TaskListProcessorEnhanced("Legacy", logger);
await processor.ProcessTasksAsync(taskFactories);
- Interface Segregation: Clean, focused interfaces for different scenarios
- Dependency Injection: Native .NET DI integration with fluent configuration
- Task Dependencies: Topological sorting and dependency resolution
- Circuit Breaker: Automatic failure detection and recovery
- Advanced Scheduling: Priority-based, dependency-aware task execution
- Streaming Results: Real-time result processing via async enumerables
- Enhanced Telemetry: OpenTelemetry integration and health monitoring
- Memory Optimization: Object pooling and efficient resource management
- Namespace Changes: Main classes moved to
TaskListProcessing.Core
- Interface Requirements: New interfaces may require additional dependencies
- Configuration Options: Enhanced options structure with validation
- Result Types: Enhanced error categorization and telemetry data
We welcome contributions! Here's how to get started:
# Clone and setup
git clone https://github.com/markhazleton/TaskListProcessor.git
cd TaskListProcessor
# Restore dependencies
dotnet restore
# Build solution
dotnet build
# Run tests
dotnet test
- π΄ Fork the repository
- πΏ Create a feature branch (
git checkout -b feature/amazing-feature
) - β¨ Make your changes with tests
- β
Verify all tests pass (
dotnet test
) - π Commit your changes (
git commit -m 'Add amazing feature'
) - π Push to the branch (
git push origin feature/amazing-feature
) - π― Open a Pull Request
- Follow C# Coding Conventions
- Add XML documentation for public APIs
- Include unit tests for new features
- Maintain backward compatibility when possible
This project is licensed under the MIT License - see the LICENSE file for details.
MIT License
Copyright (c) 2024 Mark Hazleton
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
- π Phase 1 Features Guide - Interface segregation and dependency injection
- π Migration Guide - Detailed migration instructions
- π Cleanup Summary - Recent improvements and changes
- π Complete Technical Article - Deep dive into architecture and patterns
- οΏ½ Best Practices Guide - Async programming patterns
- π₯οΈ Console Demo - Interactive demonstration
- π Web Dashboard Example - ASP.NET Core integration
- π Performance Benchmarks - Performance analysis and comparisons
- π Report Issues - Bug reports and feature requests
- π¬ Discussions - Community support and Q&A
- π§ Contact Mark - Direct contact for enterprise support
- π Website - Blog and technical articles
- πΌ LinkedIn - Professional network
- πΊ YouTube - Technical tutorials and demos
- πΈ Instagram - Behind the scenes content
Ready to supercharge your async operations? Get started with TaskListProcessor:
git clone https://github.com/markhazleton/TaskListProcessor.git
cd TaskListProcessor
dotnet run --project examples/TaskListProcessor.Console
See it in action with our interactive demo that showcases:
- π Multi-city travel data aggregation with dependency resolution
- β‘ Concurrent API calls with circuit breaker protection
- π Rich telemetry with OpenTelemetry integration
- π― Type-safe result processing with error categorization
- β±οΈ Advanced scheduling with priority-based execution
- π Streaming results via async enumerables
- ποΈ Dependency injection with decorator pattern support
Built with β€οΈ by Mark Hazleton β’ Follow for more .NET content and best practices
β If this project helped you, please consider giving it a star!