From 9a9362db564306f271e452827318a0e2fd0ec290 Mon Sep 17 00:00:00 2001 From: Volodymyr Dombrovskyi <5788605+dombrovsky@users.noreply.github.com> Date: Thu, 14 Aug 2025 23:26:55 -0600 Subject: [PATCH] more sourcedoc --- .../IDefaultTaskFlowFactory.cs | 136 +++++++ .../INamedConfigureTaskFlowChain.cs | 230 ++++++++++++ .../INamedConfigureTaskFlowOptions.cs | 100 +++++ .../INamedTaskFlowFactory.cs | 204 ++++++++++ .../ITaskFlowFactory.cs | 108 ++++++ .../ServiceCollectionExtensions.cs | 230 ++++++++++++ .../TaskFlowFactory.cs | 181 +++++++++ .../AnnotatingTaskSchedulerExtensions.cs | 162 ++++++++ .../CancelPreviousTaskSchedulerExtensions.cs | 176 +++++++++ ...ancellationScopeTaskSchedulerExtensions.cs | 131 +++++++ .../ExceptionTaskSchedulerExtensions.cs | 170 +++++++++ TaskFlow/Extensions/IOperationAnnotation.cs | 66 ++++ .../Extensions/OperationNameAnnotation.cs | 88 +++++ .../Extensions/OperationThrottledException.cs | 74 ++++ .../ThrottlingTaskSchedulerExtensions.cs | 178 +++++++++ .../TimeoutTaskSchedulerExtensions.cs | 193 ++++++++++ TaskFlow/TaskFlowSchedulerAdapter.cs | 89 +++++ TaskFlow/TaskSchedulerEnqueueExtensions.cs | 347 ++++++++++++++++++ 18 files changed, 2863 insertions(+) diff --git a/TaskFlow.Extensions.Microsoft.DependencyInjection/IDefaultTaskFlowFactory.cs b/TaskFlow.Extensions.Microsoft.DependencyInjection/IDefaultTaskFlowFactory.cs index 470fd2e..738f24f 100644 --- a/TaskFlow.Extensions.Microsoft.DependencyInjection/IDefaultTaskFlowFactory.cs +++ b/TaskFlow.Extensions.Microsoft.DependencyInjection/IDefaultTaskFlowFactory.cs @@ -1,7 +1,143 @@ namespace System.Threading.Tasks.Flow { + /// + /// Defines a factory contract for creating default instances with specified options. + /// + /// + /// + /// The interface provides a simple factory abstraction for creating + /// task flow instances with specific configuration options. This interface serves as the fallback factory + /// when no named factory is available, ensuring that task flows can always be created with at least + /// default behavior. + /// + /// + /// Key characteristics of the default factory: + /// + /// + /// Option-based creation - Takes explicit options rather than resolving them from names + /// Fallback behavior - Used when no specific named factory is registered + /// Simple interface - Single method focused on core task flow creation + /// Dependency injection ready - Designed for registration in DI containers + /// + /// + /// The default implementation typically creates standard instances, but + /// custom implementations can provide different task flow types or behaviors while maintaining + /// compatibility with the dependency injection system. + /// + /// + /// This interface is particularly useful for: + /// + /// + /// Providing consistent default behavior across the application + /// Enabling easy replacement of the default task flow implementation + /// Supporting unit testing with mock implementations + /// Allowing customization of default creation logic + /// + /// + /// + /// Custom default factory implementation: + /// + /// public class CustomDefaultTaskFlowFactory : IDefaultTaskFlowFactory + /// { + /// private readonly ILogger<CustomDefaultTaskFlowFactory> _logger; + /// + /// public CustomDefaultTaskFlowFactory(ILogger<CustomDefaultTaskFlowFactory> logger) + /// { + /// _logger = logger; + /// } + /// + /// public ITaskFlow Create(TaskFlowOptions options) + /// { + /// _logger.LogInformation("Creating TaskFlow with options: {Options}", options); + /// + /// // Create a custom task flow or wrap the standard one + /// var taskFlow = new TaskFlow(options); + /// return new LoggingTaskFlowWrapper(taskFlow, _logger); + /// } + /// } + /// + /// // Register the custom factory + /// services.AddSingleton<IDefaultTaskFlowFactory, CustomDefaultTaskFlowFactory>(); + /// + /// Usage in task flow factory: + /// + /// public class TaskFlowFactory : ITaskFlowFactory + /// { + /// private readonly IDefaultTaskFlowFactory _defaultFactory; + /// + /// public TaskFlowFactory(IDefaultTaskFlowFactory defaultFactory) + /// { + /// _defaultFactory = defaultFactory; + /// } + /// + /// public ITaskFlow CreateTaskFlow(string? name = null) + /// { + /// var options = ResolveOptions(name); + /// + /// // Use default factory for creation + /// return _defaultFactory.Create(options); + /// } + /// } + /// + /// public interface IDefaultTaskFlowFactory { + /// + /// Creates a new instance with the specified configuration options. + /// + /// The configuration options to use for creating the task flow instance. + /// A new instance configured with the specified options. + /// Thrown when is null. + /// + /// + /// This method creates a task flow instance using the provided options directly, without any + /// name-based resolution or additional configuration. The implementation should respect all + /// settings specified in the options parameter. + /// + /// + /// The created task flow should: + /// + /// + /// Honor all configuration settings from the options parameter + /// Be fully functional and ready for task enqueueing + /// Support proper disposal for resource cleanup + /// Maintain thread safety as required by the task flow contract + /// + /// + /// Implementations may choose to: + /// + /// + /// Create standard instances + /// Apply additional wrappers or decorators + /// Provide custom task flow implementations + /// Add logging, monitoring, or other cross-cutting concerns + /// + /// + /// The factory should not modify the provided options object, as it may be shared + /// across multiple factory calls or contain immutable settings. + /// + /// + /// + /// + /// public ITaskFlow Create(TaskFlowOptions options) + /// { + /// // Validate options + /// if (options == null) + /// throw new ArgumentNullException(nameof(options)); + /// + /// // Create the core task flow + /// var taskFlow = new TaskFlow(options); + /// + /// // Optionally add wrappers or decorators + /// if (options.EnableLogging) + /// { + /// taskFlow = new LoggingTaskFlowWrapper(taskFlow); + /// } + /// + /// return taskFlow; + /// } + /// + /// ITaskFlow Create(TaskFlowOptions options); } } \ No newline at end of file diff --git a/TaskFlow.Extensions.Microsoft.DependencyInjection/INamedConfigureTaskFlowChain.cs b/TaskFlow.Extensions.Microsoft.DependencyInjection/INamedConfigureTaskFlowChain.cs index 8c4d8d4..e7cb851 100644 --- a/TaskFlow.Extensions.Microsoft.DependencyInjection/INamedConfigureTaskFlowChain.cs +++ b/TaskFlow.Extensions.Microsoft.DependencyInjection/INamedConfigureTaskFlowChain.cs @@ -1,9 +1,239 @@ namespace System.Threading.Tasks.Flow { + /// + /// Defines a named configuration contract for applying scheduler chain modifications to named task flow instances. + /// + /// + /// + /// The interface enables the configuration of scheduler wrapper chains + /// for named task flow instances. This allows the application of cross-cutting concerns like error handling, + /// timeouts, throttling, and other TaskFlow extensions to specific named instances. + /// + /// + /// Scheduler chains provide: + /// + /// + /// Cross-cutting concerns - Apply common functionality without modifying core logic + /// Named customization - Different chains for different named instances + /// Composition - Chain multiple schedulers together for complex behaviors + /// Dependency injection integration - Access other services for chain configuration + /// + /// + /// The chain configuration system integrates with: + /// + /// + /// - Chains are applied after factory creation + /// - Options are used before chain application + /// TaskFlow extension methods - Chains can use any scheduler extension methods + /// + /// + /// Execution order: + /// + /// + /// Named or default factory creates the base task flow + /// Named chain configuration is applied to wrap the scheduler + /// The wrapped scheduler is used for all task operations + /// + /// + /// Common chain use cases: + /// + /// + /// Adding timeout protection to API call task flows + /// Applying error handling and logging to background processing + /// Adding throttling to prevent resource exhaustion + /// Implementing retry logic for unreliable operations + /// Adding monitoring and metrics collection + /// + /// + /// + /// Creating a named chain configuration: + /// + /// public class ApiTaskFlowChain : INamedConfigureTaskFlowChain + /// { + /// private readonly ILogger<ApiTaskFlowChain> _logger; + /// private readonly IMetrics _metrics; + /// + /// public ApiTaskFlowChain(ILogger<ApiTaskFlowChain> logger, IMetrics metrics) + /// { + /// _logger = logger; + /// _metrics = metrics; + /// } + /// + /// public string Name => "api"; + /// + /// public ITaskScheduler ConfigureChain(ITaskScheduler taskScheduler) + /// { + /// return taskScheduler + /// .WithOperationName("ApiOperation") + /// .WithTimeout(TimeSpan.FromSeconds(30)) + /// .OnError<HttpRequestException>((sched, ex, name) => + /// _logger.LogWarning(ex, "HTTP error in {Operation}", name?.OperationName)) + /// .OnError<TimeoutException>((sched, ex, name) => + /// _metrics.IncrementCounter("api.timeout", new { operation = name?.OperationName })); + /// } + /// } + /// + /// Registering named chains: + /// + /// // Register using implementation + /// services.AddSingleton<INamedConfigureTaskFlowChain, ApiTaskFlowChain>(); + /// + /// // Register using delegate + /// services.AddTaskFlow("background", + /// configureSchedulerChain: (scheduler, provider) => { + /// var logger = provider.GetRequiredService<ILogger>(); + /// return scheduler + /// .WithOperationName("BackgroundWork") + /// .OnError(ex => logger.LogError(ex, "Background processing error")); + /// }); + /// + /// Complex chain configuration: + /// + /// public ITaskScheduler ConfigureChain(ITaskScheduler taskScheduler) + /// { + /// var baseScheduler = taskScheduler + /// .WithOperationName($"{Name}Operation") + /// .WithTimeout(TimeSpan.FromMinutes(5)); + /// + /// // Add environment-specific behavior + /// if (_environment.IsProduction()) + /// { + /// baseScheduler = baseScheduler + /// .WithDebounce(TimeSpan.FromSeconds(1)) + /// .OnError<Exception>(ex => _telemetry.TrackException(ex)); + /// } + /// else + /// { + /// baseScheduler = baseScheduler + /// .OnError<Exception>(ex => _console.WriteLine($"Error: {ex}")); + /// } + /// + /// return baseScheduler; + /// } + /// + /// public interface INamedConfigureTaskFlowChain { + /// + /// Gets the name that identifies this scheduler chain configuration. + /// + /// + /// A string that uniquely identifies this chain configuration within the dependency injection container. + /// This name is used to resolve which chain should be applied when creating named task flow instances. + /// + /// + /// + /// The name serves as the key for chain resolution in the dependency injection system. + /// When is called with a specific name, + /// the factory system searches for a registered with a + /// matching property. + /// + /// + /// Name coordination considerations: + /// + /// + /// Consistency - Should match names used in factories and options for coordinated configuration + /// Case sensitivity - Names are typically case-sensitive + /// Uniqueness - Each chain configuration should have a unique name within the container + /// Stability - The name should remain constant throughout the configuration's lifetime + /// + /// + /// The chain is applied after the base task flow is created (either by a named factory or the default factory) + /// but before the task flow is returned to the calling code. This allows the chain to wrap the scheduler + /// with additional functionality. + /// + /// + /// + /// + /// public class DatabaseChain : INamedConfigureTaskFlowChain + /// { + /// // This chain will be applied when CreateTaskFlow("database") is called + /// public string Name => "database"; + /// + /// public ITaskScheduler ConfigureChain(ITaskScheduler taskScheduler) + /// { + /// return taskScheduler + /// .WithOperationName("DatabaseOperation") + /// .WithTimeout(TimeSpan.FromSeconds(30)) + /// .OnError<SqlException>(ex => _logger.LogError(ex, "Database error")); + /// } + /// } + /// + /// string Name { get; } + /// + /// Configures and returns a scheduler chain by wrapping the provided task scheduler with additional functionality. + /// + /// The base task scheduler to wrap with additional functionality. + /// An that wraps the original scheduler with the configured chain. + /// Thrown when is null. + /// + /// + /// This method applies scheduler wrapper chains to add cross-cutting concerns and additional functionality + /// to the base task scheduler. The method can chain multiple scheduler extensions together to create + /// complex behaviors while maintaining the core scheduler interface. + /// + /// + /// Chain configuration guidelines: + /// + /// + /// Composition - Use TaskFlow extension methods to build the chain + /// Order matters - The order of chained extensions affects behavior + /// Preserve interface - Always return an ITaskScheduler implementation + /// Resource management - Ensure proper disposal is supported throughout the chain + /// + /// + /// Available extension methods for chaining include: + /// + /// + /// - Add operation naming + /// - Add timeout protection + /// - Add debouncing + /// - Add error handling + /// - Add cancellation scopes + /// - Add cancel-previous behavior + /// + /// + /// The method can access dependency injection services to configure the chain based on runtime conditions, + /// configuration settings, or other application state. + /// + /// + /// Chain execution order is determined by the order of method calls, with each extension wrapping + /// the previous one. The outermost wrapper receives calls first, and the original scheduler + /// receives calls last. + /// + /// + /// + /// + /// public ITaskScheduler ConfigureChain(ITaskScheduler taskScheduler) + /// { + /// if (taskScheduler == null) + /// throw new ArgumentNullException(nameof(taskScheduler)); + /// + /// // Build a comprehensive chain for this named instance + /// var chain = taskScheduler + /// .WithOperationName(Name) // Add operation naming + /// .WithTimeout(TimeSpan.FromMinutes(2)); // Add timeout protection + /// + /// // Add environment-specific extensions + /// if (_configuration.GetValue<bool>("EnableThrottling")) + /// { + /// var interval = _configuration.GetValue<TimeSpan>("ThrottleInterval"); + /// chain = chain.WithDebounce(interval); + /// } + /// + /// // Add error handling + /// chain = chain + /// .OnError<TimeoutException>((sched, ex, name) => + /// _metrics.IncrementCounter("timeout", new { operation = name?.OperationName })) + /// .OnError<Exception>(ex => + /// _logger.LogError(ex, "Error in {Name} operation", Name)); + /// + /// return chain; + /// } + /// + /// ITaskScheduler ConfigureChain(ITaskScheduler taskScheduler); } } \ No newline at end of file diff --git a/TaskFlow.Extensions.Microsoft.DependencyInjection/INamedConfigureTaskFlowOptions.cs b/TaskFlow.Extensions.Microsoft.DependencyInjection/INamedConfigureTaskFlowOptions.cs index 07bdbee..8499f12 100644 --- a/TaskFlow.Extensions.Microsoft.DependencyInjection/INamedConfigureTaskFlowOptions.cs +++ b/TaskFlow.Extensions.Microsoft.DependencyInjection/INamedConfigureTaskFlowOptions.cs @@ -1,9 +1,109 @@ namespace System.Threading.Tasks.Flow { + /// + /// Defines a named configuration contract for providing specific to named task flow instances. + /// + /// + /// + /// The interface enables named configuration of task flow options + /// within the dependency injection system. This allows different task flow instances to have different + /// configuration settings based on their intended purpose or usage context. + /// + /// + /// Named options configuration provides: + /// + /// + /// Configuration separation - Different options for different named instances + /// Runtime configuration - Options can be computed or resolved at runtime + /// Dependency injection integration - Can access other services for configuration + /// Environment-specific settings - Different options for different environments + /// + /// + /// The options configuration system works in conjunction with: + /// + /// + /// - For custom task flow creation + /// - For scheduler chain configuration + /// - As the fallback when no named factory exists + /// + /// + /// Configuration precedence: + /// + /// + /// Named options are resolved and applied first + /// If no named options exist, default options are used + /// Options are passed to either named factories or the default factory + /// Scheduler chains are applied to the resulting task flow + /// + /// public interface INamedConfigureTaskFlowOptions { + /// + /// Gets the name that identifies this options configuration. + /// + /// + /// A string that uniquely identifies this options configuration within the dependency injection container. + /// This name is used to resolve which options should be used when creating named task flow instances. + /// + /// + /// + /// The name serves as the key for options resolution in the dependency injection system. + /// When is called with a specific name, + /// the factory system searches for a registered with a + /// matching property. + /// + /// + /// Name matching considerations: + /// + /// + /// Case sensitivity - Names are typically case-sensitive + /// Uniqueness - Each options configuration should have a unique name within the container + /// Stability - The name should remain constant throughout the configuration's lifetime + /// Coordination - Should match names used in factories and scheduler chains for consistency + /// + /// + /// If multiple options configurations are registered with the same name, the behavior depends on the + /// dependency injection container implementation, but typically the last registered configuration + /// will be used. + /// + /// string Name { get; } + /// + /// Creates and configures a instance for this named configuration. + /// + /// A instance configured according to this configuration's requirements. + /// + /// + /// This method is responsible for creating and configuring the options that will be used to + /// create task flow instances with the associated name. The method can access dependency injection + /// services, configuration sources, or any other resources needed to determine the appropriate + /// configuration. + /// + /// + /// Configuration guidelines: + /// + /// + /// Consistency - Should return consistent options for the same conditions + /// Validation - Should ensure returned options are valid and usable + /// Performance - Should be efficient as it may be called multiple times + /// Thread safety - Should be safe for concurrent access + /// + /// + /// The method can: + /// + /// + /// Read from configuration files or environment variables + /// Access other dependency injection services + /// Compute options based on runtime conditions + /// Provide different options for different environments + /// Apply business logic to determine appropriate settings + /// + /// + /// The returned options should be a new instance rather than a shared static instance to + /// avoid potential modification issues, unless the options are truly immutable. + /// + /// TaskFlowOptions Configure(); } } \ No newline at end of file diff --git a/TaskFlow.Extensions.Microsoft.DependencyInjection/INamedTaskFlowFactory.cs b/TaskFlow.Extensions.Microsoft.DependencyInjection/INamedTaskFlowFactory.cs index 2566bea..db9ecea 100644 --- a/TaskFlow.Extensions.Microsoft.DependencyInjection/INamedTaskFlowFactory.cs +++ b/TaskFlow.Extensions.Microsoft.DependencyInjection/INamedTaskFlowFactory.cs @@ -1,9 +1,213 @@ namespace System.Threading.Tasks.Flow { + /// + /// Defines a named factory contract for creating instances with specific configurations. + /// + /// + /// + /// The interface extends the factory pattern to support named + /// configurations, allowing multiple task flow factories to coexist within the same dependency + /// injection container. Each factory is associated with a specific name and provides custom + /// creation logic for that particular configuration. + /// + /// + /// Named factories enable: + /// + /// + /// Multiple configurations - Different task flow types for different purposes + /// Specialized implementations - Custom task flow types for specific scenarios + /// Environment-specific behavior - Different implementations for testing vs. production + /// Feature-specific optimizations - Tailored task flows for specific application features + /// + /// + /// The factory system works hierarchically: + /// + /// + /// Named factories are checked first when creating task flows + /// If no named factory exists, the default factory is used + /// Options and scheduler chains can still be applied to named factory results + /// + /// + /// Common use cases include: + /// + /// + /// Background processing with dedicated thread task flows + /// UI operations with current thread task flows + /// High-throughput scenarios with custom parallel implementations + /// Testing scenarios with mock or instrumented task flows + /// + /// + /// + /// Creating a custom named factory: + /// + /// public class BackgroundTaskFlowFactory : INamedTaskFlowFactory + /// { + /// public string Name => "background"; + /// + /// public ITaskFlow Create(TaskFlowOptions options) + /// { + /// // Create a dedicated thread task flow for background processing + /// return new DedicatedThreadTaskFlow(options); + /// } + /// } + /// + /// public class UiTaskFlowFactory : INamedTaskFlowFactory + /// { + /// public string Name => "ui"; + /// + /// public ITaskFlow Create(TaskFlowOptions options) + /// { + /// // Create a current thread task flow for UI operations + /// return new CurrentThreadTaskFlow(options); + /// } + /// } + /// + /// Registering named factories: + /// + /// // Register multiple named factories + /// services.AddSingleton<INamedTaskFlowFactory, BackgroundTaskFlowFactory>(); + /// services.AddSingleton<INamedTaskFlowFactory, UiTaskFlowFactory>(); + /// + /// // Register using delegate factory + /// services.AddTaskFlow("database", (provider, options) => + /// new TaskFlow(options with { MaxConcurrency = 1 })); + /// + /// Using named factories: + /// + /// public class ApplicationService + /// { + /// private readonly ITaskFlowFactory _factory; + /// + /// public ApplicationService(ITaskFlowFactory factory) + /// { + /// _factory = factory; + /// } + /// + /// public async Task ProcessInBackgroundAsync() + /// { + /// using var taskFlow = _factory.CreateTaskFlow("background"); + /// await taskFlow.Enqueue(() => DoBackgroundWorkAsync()); + /// } + /// + /// public async Task UpdateUiAsync() + /// { + /// using var taskFlow = _factory.CreateTaskFlow("ui"); + /// await taskFlow.Enqueue(() => UpdateUserInterfaceAsync()); + /// } + /// } + /// + /// public interface INamedTaskFlowFactory { + /// + /// Gets the name that identifies this factory configuration. + /// + /// + /// A string that uniquely identifies this factory within the dependency injection container. + /// This name is used to resolve which factory should be used when creating named task flow instances. + /// + /// + /// + /// The name serves as the key for factory resolution in the dependency injection system. + /// When is called with a specific name, + /// the factory system searches for a registered with a + /// matching property. + /// + /// + /// Name matching considerations: + /// + /// + /// Case sensitivity - Names are typically case-sensitive + /// Uniqueness - Each factory should have a unique name within the container + /// Stability - The name should remain constant throughout the factory's lifetime + /// Null handling - Names should not be null (use empty string for default if needed) + /// + /// + /// If multiple factories are registered with the same name, the behavior depends on the + /// dependency injection container implementation, but typically the last registered factory + /// will be used. + /// + /// + /// + /// + /// public class DatabaseTaskFlowFactory : INamedTaskFlowFactory + /// { + /// // This factory will be used when CreateTaskFlow("database") is called + /// public string Name => "database"; + /// + /// public ITaskFlow Create(TaskFlowOptions options) + /// { + /// // Create task flow optimized for database operations + /// return new TaskFlow(options with { MaxConcurrency = 1 }); + /// } + /// } + /// + /// string Name { get; } + /// + /// Creates a new instance with the specified configuration options. + /// + /// The configuration options to use for creating the task flow instance. + /// A new instance configured according to this factory's implementation and the provided options. + /// Thrown when is null. + /// + /// + /// This method implements the core creation logic for this named factory. Unlike the default + /// factory, named factories can provide completely custom implementations, different task flow + /// types, or specialized configurations based on their intended purpose. + /// + /// + /// Implementation guidelines: + /// + /// + /// Options respect - Should honor relevant settings from the options parameter + /// Consistent behavior - Should create task flows with predictable characteristics + /// Resource management - Created task flows should support proper disposal + /// Thread safety - The factory method should be thread-safe for concurrent calls + /// + /// + /// Named factories can: + /// + /// + /// Create different task flow implementations (e.g., , ) + /// Apply specific option modifications or defaults + /// Add decorators or wrappers for logging, monitoring, or other concerns + /// Integrate with external systems or configuration sources + /// + /// + /// The factory should not modify the provided options object directly, as it may be shared + /// across multiple factory calls. If option modifications are needed, create a copy or use + /// immutable update patterns. + /// + /// + /// + /// + /// public ITaskFlow Create(TaskFlowOptions options) + /// { + /// // Validate input + /// if (options == null) + /// throw new ArgumentNullException(nameof(options)); + /// + /// // Create specialized task flow for this factory's purpose + /// ITaskFlow taskFlow = Name switch + /// { + /// "background" => new DedicatedThreadTaskFlow(options), + /// "ui" => new CurrentThreadTaskFlow(options), + /// "parallel" => new TaskFlow(options with { MaxConcurrency = Environment.ProcessorCount }), + /// _ => new TaskFlow(options) + /// }; + /// + /// // Add any common decorators + /// if (_enableLogging) + /// { + /// taskFlow = new LoggingTaskFlowWrapper(taskFlow, _logger); + /// } + /// + /// return taskFlow; + /// } + /// + /// ITaskFlow Create(TaskFlowOptions options); } } \ No newline at end of file diff --git a/TaskFlow.Extensions.Microsoft.DependencyInjection/ITaskFlowFactory.cs b/TaskFlow.Extensions.Microsoft.DependencyInjection/ITaskFlowFactory.cs index f7fbcab..65a2fb0 100644 --- a/TaskFlow.Extensions.Microsoft.DependencyInjection/ITaskFlowFactory.cs +++ b/TaskFlow.Extensions.Microsoft.DependencyInjection/ITaskFlowFactory.cs @@ -1,7 +1,115 @@ namespace System.Threading.Tasks.Flow { + /// + /// Defines a factory contract for creating instances with optional naming support. + /// + /// + /// + /// The interface provides a standardized way to create task flow instances + /// within dependency injection containers. It supports named instances, allowing multiple task flow + /// configurations to coexist within the same application. + /// + /// + /// This factory interface is the primary entry point for creating task flows in dependency injection + /// scenarios. It abstracts the complexity of: + /// + /// + /// Named configuration resolution + /// Custom factory delegation + /// Option configuration + /// Scheduler chain configuration + /// Default factory fallback + /// + /// + /// The factory supports multiple configuration patterns: + /// + /// + /// Default instances - Created with default options when no name is specified + /// Named instances - Created with specific configurations based on the provided name + /// Custom factories - Delegate to user-provided factory implementations + /// Chained schedulers - Apply scheduler wrapper chains for cross-cutting concerns + /// + /// + /// + /// Basic factory usage in dependency injection: + /// + /// // Register TaskFlow services + /// services.AddTaskFlow(); + /// + /// // Inject and use the factory + /// public class SomeService + /// { + /// private readonly ITaskFlowFactory _taskFlowFactory; + /// + /// public SomeService(ITaskFlowFactory taskFlowFactory) + /// { + /// _taskFlowFactory = taskFlowFactory; + /// } + /// + /// public async Task ProcessDataAsync() + /// { + /// using var taskFlow = _taskFlowFactory.CreateTaskFlow(); + /// await taskFlow.Enqueue(() => DoWorkAsync()); + /// } + /// } + /// + /// public interface ITaskFlowFactory { + /// + /// Creates a new instance with the specified name or default configuration. + /// + /// + /// The optional name identifying which configuration to use. If null or empty, + /// the default configuration is used. + /// + /// + /// A new instance configured according to the specified name or default settings. + /// + /// + /// + /// This method creates task flow instances based on the configuration associated with the provided name. + /// The factory resolves the configuration through the following precedence: + /// + /// + /// Named factory - If a custom factory is registered for the name, it is used + /// Default factory - If no named factory exists, the default factory is used + /// Options configuration - Named options are applied if registered + /// Scheduler chains - Named scheduler chains are applied if registered + /// Fallback - Default options and configuration are used if nothing else is found + /// + /// + /// The name parameter supports: + /// + /// + /// null - Uses default configuration (equivalent to empty string) + /// Empty string - Uses default configuration + /// Named string - Uses configuration registered with that specific name + /// + /// + /// Created task flows should be properly disposed when no longer needed to ensure + /// resource cleanup and proper shutdown of any background operations. + /// + /// + /// If multiple configurations are registered with the same name, the behavior depends + /// on the specific registration order and DI container implementation. Generally, + /// the last registered configuration takes precedence. + /// + /// + /// + /// + /// // Create default task flow + /// using var defaultFlow = factory.CreateTaskFlow(); + /// + /// // Create named task flow + /// using var namedFlow = factory.CreateTaskFlow("background-processor"); + /// + /// // Both are equivalent for default configuration + /// using var flow1 = factory.CreateTaskFlow(null); + /// using var flow2 = factory.CreateTaskFlow(""); + /// using var flow3 = factory.CreateTaskFlow(); + /// + /// ITaskFlow CreateTaskFlow(string? name = null); } } \ No newline at end of file diff --git a/TaskFlow.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs b/TaskFlow.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs index f781432..d1a8d2f 100644 --- a/TaskFlow.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs +++ b/TaskFlow.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs @@ -5,13 +5,128 @@ namespace System.Threading.Tasks.Flow using System.Threading.Tasks.Flow.Annotations; using System.Threading.Tasks.Flow.Internal; + /// + /// Provides extension methods for to register TaskFlow services and configurations in dependency injection containers. + /// + /// + /// + /// The class serves as the primary entry point for integrating + /// TaskFlow with Microsoft's dependency injection system. It provides a comprehensive set of extension methods + /// that enable registration of task flow services with various configuration options and customization points. + /// + /// + /// Key registration features: + /// + /// + /// Default registration - Simple registration with default settings + /// Options-based registration - Registration with explicit configuration options + /// Named configurations - Support for multiple named task flow configurations + /// Custom factories - Integration with custom task flow creation logic + /// Scheduler chains - Configuration of cross-cutting concerns through wrapper chains + /// + /// + /// Service lifetime patterns: + /// + /// + /// Singleton factories - Configuration and factory services are registered as singletons + /// Scoped instances - Task flow instances are scoped to dependency injection scopes + /// Automatic disposal - Task flows are automatically disposed when scopes end + /// + /// + /// The extension methods register the following services: + /// + /// + /// - Factory for creating task flow instances + /// - Task scheduler interface implemented by scoped task flows + /// - Information interface implemented by scoped task flows + /// - Default factory for standard task flow creation + /// + /// + /// Note that itself is not registered directly. Instead, scoped wrapper + /// instances provide the and interfaces, + /// allowing controlled access to task flow functionality while ensuring proper lifecycle management. + /// + /// public static class ServiceCollectionExtensions { + /// + /// Adds TaskFlow services to the specified with default configuration. + /// + /// The to add TaskFlow services to. + /// The for method chaining. + /// Thrown when is null. + /// + /// + /// This is the simplest registration method that adds TaskFlow services with default settings. + /// It registers all necessary services for basic TaskFlow functionality without any custom configuration. + /// + /// + /// Services registered: + /// + /// + /// as singleton + /// as singleton + /// as scoped + /// as scoped + /// + /// + /// The scoped services provide access to a task flow instance that is created per dependency injection + /// scope and automatically disposed when the scope ends. This ensures proper resource management + /// and clean shutdown of background operations. + /// + /// + /// + /// + /// public void ConfigureServices(IServiceCollection services) + /// { + /// services.AddTaskFlow(); + /// + /// services.AddScoped<MyService>(); + /// } + /// + /// public class MyService + /// { + /// private readonly ITaskScheduler _scheduler; + /// + /// public MyService(ITaskScheduler scheduler) + /// { + /// _scheduler = scheduler; + /// } + /// + /// public Task DoWorkAsync() => _scheduler.Enqueue(() => ProcessDataAsync()); + /// } + /// + /// public static IServiceCollection AddTaskFlow(this IServiceCollection services) { return AddTaskFlow(services, name: null); } + /// + /// Adds TaskFlow services to the specified with the specified options. + /// + /// The to add TaskFlow services to. + /// The to use for configuring the default task flow instances. + /// The for method chaining. + /// Thrown when or is null. + /// + /// + /// This method registers TaskFlow services with explicit configuration options that will be used + /// for all default task flow instances. The options are applied to the default (unnamed) configuration. + /// + /// + /// The provided options object is used directly, so it should not be modified after registration. + /// If you need different options for different scenarios, consider using named configurations instead. + /// + /// + /// + /// + /// services.AddTaskFlow(new TaskFlowOptions + /// { + /// SynchronousDisposeTimeout = TimeSpan.FromSeconds(30) + /// }); + /// + /// public static IServiceCollection AddTaskFlow(this IServiceCollection services, TaskFlowOptions options) { Argument.NotNull(options); @@ -19,6 +134,39 @@ public static IServiceCollection AddTaskFlow(this IServiceCollection services, T return AddTaskFlow(services, name: null, baseTaskFlowFactory: null, _ => options, configureSchedulerChain: null); } + /// + /// Adds TaskFlow services to the specified with a named configuration and specified options. + /// + /// The to add TaskFlow services to. + /// The name to associate with this configuration. Must not be null or empty. + /// The to use for configuring task flow instances with this name. + /// The for method chaining. + /// Thrown when or is null. + /// Thrown when is null or empty. + /// + /// + /// This method registers a named configuration that can be accessed by calling + /// with the specified name. This enables + /// different parts of an application to use task flows with different configurations. + /// + /// + /// Named configurations are additive - you can register multiple named configurations and they + /// will coexist without interfering with each other or the default configuration. + /// + /// + /// + /// + /// // Register database configuration for single-threaded access + /// services.AddTaskFlow("database", new TaskFlowOptions { SynchronousDisposeTimeout = TimeSpan.FromSeconds(30) }); + /// + /// // Register API configuration for high concurrency + /// services.AddTaskFlow("api", new TaskFlowOptions { SynchronousDisposeTimeout = TimeSpan.FromSeconds(10) }); + /// + /// // Usage + /// var databaseFlow = factory.CreateTaskFlow("database"); + /// var apiFlow = factory.CreateTaskFlow("api"); + /// + /// public static IServiceCollection AddTaskFlow(this IServiceCollection services, string name, TaskFlowOptions options) { Argument.NotEmpty(name); @@ -27,6 +175,88 @@ public static IServiceCollection AddTaskFlow(this IServiceCollection services, s return AddTaskFlow(services, name, baseTaskFlowFactory: null, _ => options, configureSchedulerChain: null); } + /// + /// Adds TaskFlow services to the specified with comprehensive configuration options. + /// + /// The to add TaskFlow services to. + /// The optional name to associate with this configuration. If null, applies to the default configuration. + /// An optional custom factory function for creating the base task flow instances. + /// An optional function for configuring task flow options based on the service provider. + /// An optional function for configuring the scheduler chain to apply cross-cutting concerns. + /// The for method chaining. + /// Thrown when is null. + /// + /// + /// This is the most comprehensive registration method that provides full control over task flow creation + /// and configuration. It allows customization of every aspect of the task flow creation process through + /// the provided delegate functions. + /// + /// + /// Configuration precedence and application order: + /// + /// + /// is called to determine the options + /// is called to create the base task flow (or default factory if null) + /// is called to wrap the scheduler with additional functionality + /// + /// + /// All delegate functions receive the as a parameter, allowing them to + /// access other registered services for configuration purposes. This enables complex configuration + /// scenarios based on runtime conditions, configuration files, or other services. + /// + /// + /// Factory and chain delegates are only called when task flows are actually created, not during + /// service registration. This enables lazy evaluation and access to scoped services if needed. + /// + /// + /// + /// Custom factory with dependency injection: + /// + /// services.AddTaskFlow("custom", + /// baseTaskFlowFactory: (provider, options) => { + /// var logger = provider.GetRequiredService<ILogger<TaskFlow>>(); + /// var taskFlow = new TaskFlow(options); + /// return new LoggingTaskFlowWrapper(taskFlow, logger); + /// }); + /// + /// Dynamic options configuration: + /// + /// services.AddTaskFlow("adaptive", + /// configureOptions: provider => { + /// var config = provider.GetRequiredService<IConfiguration>(); + /// var environment = provider.GetRequiredService<IWebHostEnvironment>(); + /// + /// return new TaskFlowOptions + /// { + /// SynchronousDisposeTimeout = environment.IsDevelopment() ? TimeSpan.FromSeconds(30) : config.GetValue<int>("TaskFlow:SynchronousDisposeTimeout") + /// }; + /// }); + /// + /// Comprehensive scheduler chain: + /// + /// services.AddTaskFlow("robust", + /// configureSchedulerChain: (scheduler, provider) => { + /// var logger = provider.GetRequiredService<ILogger>(); + /// var metrics = provider.GetRequiredService<IMetrics>(); + /// var config = provider.GetRequiredService<IConfiguration>(); + /// + /// var chain = scheduler + /// .WithOperationName("RobustOperation") + /// .WithTimeout(TimeSpan.FromSeconds(config.GetValue<int>("Timeout", 30))) + /// .OnError<Exception>(ex => { + /// logger.LogError(ex, "Operation failed"); + /// metrics.IncrementCounter("errors"); + /// }); + /// + /// if (config.GetValue<bool>("EnableThrottling")) + /// { + /// chain = chain.WithDebounce(TimeSpan.FromMilliseconds(500)); + /// } + /// + /// return chain; + /// }); + /// + /// public static IServiceCollection AddTaskFlow( this IServiceCollection services, string? name, diff --git a/TaskFlow.Extensions.Microsoft.DependencyInjection/TaskFlowFactory.cs b/TaskFlow.Extensions.Microsoft.DependencyInjection/TaskFlowFactory.cs index f9d003f..ea9aed2 100644 --- a/TaskFlow.Extensions.Microsoft.DependencyInjection/TaskFlowFactory.cs +++ b/TaskFlow.Extensions.Microsoft.DependencyInjection/TaskFlowFactory.cs @@ -5,6 +5,103 @@ namespace System.Threading.Tasks.Flow using System.Threading.Tasks.Flow.Annotations; using System.Threading.Tasks.Flow.Internal; + /// + /// Provides a comprehensive factory implementation for creating instances with support for named configurations, custom factories, and scheduler chains. + /// + /// + /// + /// The class serves as the central orchestrator for task flow creation in + /// dependency injection scenarios. It coordinates multiple configuration sources to create appropriately + /// configured task flow instances based on named configurations or default settings. + /// + /// + /// The factory implements a sophisticated resolution system that supports: + /// + /// + /// Named factories - Custom factory implementations for specific configurations + /// Named options - Configuration objects specific to named instances + /// Scheduler chains - Cross-cutting concern application through wrapper chains + /// Default fallback - Consistent behavior when no named configuration exists + /// + /// + /// Resolution algorithm: + /// + /// + /// Name normalization - Null names are converted to empty strings + /// Configuration lookup - Search for named factories, options, and chains + /// Options resolution - Use named options or fall back to defaults + /// Factory selection - Use named factory or default factory for creation + /// Chain application - Apply scheduler chains if configured + /// Wrapper creation - Create ownership wrapper if chains are applied + /// + /// + /// The factory is designed to work seamlessly with Microsoft's dependency injection container + /// and supports multiple named configurations within the same application. This enables different + /// parts of an application to use task flows with different characteristics and behaviors. + /// + /// + /// + /// Basic factory registration and usage: + /// + /// // Register TaskFlow services in dependency injection + /// services.AddTaskFlow(); + /// + /// // Use the factory + /// public class DocumentProcessor + /// { + /// private readonly ITaskFlowFactory _factory; + /// + /// public DocumentProcessor(ITaskFlowFactory factory) + /// { + /// _factory = factory; + /// } + /// + /// public async Task ProcessDocumentAsync() + /// { + /// using var taskFlow = _factory.CreateTaskFlow(); + /// await taskFlow.Enqueue(() => ProcessDocumentContentAsync()); + /// } + /// } + /// + /// Named configuration setup: + /// + /// // Register multiple named configurations + /// services.AddTaskFlow("database", new TaskFlowOptions { ... }); + /// services.AddTaskFlow("api", new TaskFlowOptions { ... }); + /// services.AddTaskFlow("background", + /// configureSchedulerChain: (scheduler, provider) => + /// scheduler.WithTimeout(TimeSpan.FromMinutes(30))); + /// + /// // Use named configurations + /// var databaseFlow = factory.CreateTaskFlow("database"); + /// var apiFlow = factory.CreateTaskFlow("api"); + /// var backgroundFlow = factory.CreateTaskFlow("background"); // With 30-minute timeout + /// + /// Custom factory and chain integration: + /// + /// // Custom named factory + /// public class UiTaskFlowFactory : INamedTaskFlowFactory + /// { + /// public string Name => "ui"; + /// public ITaskFlow Create(TaskFlowOptions options) => new CurrentThreadTaskFlow(options); + /// } + /// + /// // Custom chain configuration + /// public class LoggingChain : INamedConfigureTaskFlowChain + /// { + /// public string Name => "ui"; + /// public ITaskScheduler ConfigureChain(ITaskScheduler scheduler) => + /// scheduler.OnError(ex => _logger.LogError(ex, "UI operation failed")); + /// } + /// + /// // Registration + /// services.AddSingleton<INamedTaskFlowFactory, UiTaskFlowFactory>(); + /// services.AddSingleton<INamedConfigureTaskFlowChain, LoggingChain>(); + /// + /// // Usage creates CurrentThreadTaskFlow with error logging + /// var uiFlow = factory.CreateTaskFlow("ui"); + /// + /// public class TaskFlowFactory : ITaskFlowFactory { private readonly IEnumerable _namedTaskFlowFactories; @@ -12,6 +109,33 @@ public class TaskFlowFactory : ITaskFlowFactory private readonly IEnumerable _namedConfigureTaskFlowOptions; private readonly IDefaultTaskFlowFactory _defaultTaskFlowFactory; + /// + /// Initializes a new instance of the class with the specified configuration dependencies. + /// + /// A collection of named task flow factories for custom creation logic. + /// A collection of named scheduler chain configurations for applying cross-cutting concerns. + /// A collection of named options configurations for different task flow instances. + /// The default factory to use when no named factory is available. + /// Thrown when any of the parameters is null. + /// + /// + /// This constructor sets up the factory with all necessary dependencies for comprehensive task flow creation. + /// The dependencies are typically provided by the dependency injection container and represent the complete + /// configuration space for task flow creation. + /// + /// + /// Collection parameters can be empty but should not be null: + /// + /// + /// - Empty collection means no named factories are registered + /// - Empty collection means no scheduler chains are configured + /// - Empty collection means no named options are configured + /// + /// + /// The is required and serves as the fallback for creating + /// task flows when no named factory is available for a particular name. + /// + /// public TaskFlowFactory( IEnumerable namedTaskFlowFactories, IEnumerable namedConfigureTaskFlowChains, @@ -29,6 +153,63 @@ public TaskFlowFactory( _defaultTaskFlowFactory = defaultTaskFlowFactory; } + /// + /// Creates a new instance using the configuration associated with the specified name. + /// + /// + /// The optional name identifying which configuration to use. If null or empty, + /// the default configuration is used. + /// + /// + /// A new instance configured according to the specified name or default settings. + /// The returned instance may be wrapped with scheduler chains if configured. + /// + /// + /// + /// This method implements the core factory logic by orchestrating the resolution and application + /// of named configurations. The creation process follows these steps: + /// + /// + /// Name normalization - Converts null to empty string for consistent lookup + /// Configuration resolution - Searches for named factories, chains, and options + /// Options determination - Uses named options or defaults to TaskFlowOptions.Default + /// Base creation - Creates task flow using named factory or default factory + /// Chain application - Applies scheduler chain if one is configured for the name + /// Wrapper creation - Wraps with ownership wrapper if chains were applied + /// + /// + /// Resolution behavior: + /// + /// + /// Single match expected - Uses SingleOrDefault() to find configurations, expects at most one match per name + /// Graceful fallback - Missing named configurations result in default behavior rather than errors + /// Chain independence - Chains are applied regardless of whether a named factory was used + /// + /// + /// The method creates a new task flow instance on each call - it does not cache or reuse instances. + /// Callers are responsible for properly disposing the returned task flow instances. + /// + /// + /// If multiple configurations are registered with the same name, the SingleOrDefault() call will + /// throw an exception. This is by design to prevent ambiguous configuration scenarios. + /// + /// + /// + /// + /// // Create with default configuration + /// var defaultFlow = factory.CreateTaskFlow(); + /// var alsoDefaultFlow = factory.CreateTaskFlow(""); + /// var nullDefaultFlow = factory.CreateTaskFlow(null); + /// + /// // Create with named configuration + /// var databaseFlow = factory.CreateTaskFlow("database"); + /// var apiFlow = factory.CreateTaskFlow("api"); + /// + /// // All instances should be disposed when done + /// using var flow = factory.CreateTaskFlow("background"); + /// await flow.Enqueue(() => DoWorkAsync()); + /// + /// public ITaskFlow CreateTaskFlow(string? name = null) { name ??= string.Empty; diff --git a/TaskFlow/Extensions/AnnotatingTaskSchedulerExtensions.cs b/TaskFlow/Extensions/AnnotatingTaskSchedulerExtensions.cs index 10a473e..f920562 100644 --- a/TaskFlow/Extensions/AnnotatingTaskSchedulerExtensions.cs +++ b/TaskFlow/Extensions/AnnotatingTaskSchedulerExtensions.cs @@ -3,8 +3,110 @@ namespace System.Threading.Tasks.Flow using System.Linq; using System.Threading.Tasks.Flow.Annotations; + /// + /// Provides extension methods for to add operation annotation capabilities. + /// + /// + /// + /// This class enables annotating task scheduler operations with metadata that can be used for + /// monitoring, debugging, error handling, and other cross-cutting concerns. Annotations are + /// attached to operations through extended state and can be retrieved by other extension methods + /// in the TaskFlow pipeline. + /// + /// + /// The annotation system works by: + /// + /// + /// Storing annotation data in extended state objects + /// Providing methods to enqueue operations with annotation access + /// Enabling retrieval of annotations by type in downstream operations + /// + /// + /// Common use cases include: + /// + /// + /// Adding operation names for logging and debugging + /// Storing correlation IDs for distributed tracing + /// Attaching user context for authorization + /// Providing metadata for monitoring and metrics + /// + /// + /// + /// Basic operation naming: + /// + /// ITaskScheduler scheduler = // ... obtain scheduler + /// + /// // Add operation name annotation + /// var namedScheduler = scheduler.WithOperationName("ProcessUserData"); + /// + /// // Enqueue operation with annotation access + /// await namedScheduler.AnnotatedEnqueue<string, OperationNameAnnotation>( + /// (state, annotation, token) => { + /// Console.WriteLine($"Executing: {annotation?.OperationName}") + /// return ValueTask.FromResult("completed"); + /// }, + /// state: null, + /// CancellationToken.None); + /// + /// Custom annotation integration: + /// + /// public class UserContextAnnotation : IOperationAnnotation + /// { + /// public string UserId { get; set; } + /// public string Role { get; set; } + /// } + /// + /// var contextScheduler = scheduler.WithExtendedState(new UserContextAnnotation + /// { + /// UserId = "user123", + /// Role = "admin" + /// }); + /// + /// await contextScheduler.AnnotatedEnqueue<void, UserContextAnnotation>( + /// (state, context, token) => { + /// if (context?.Role == "admin") { + /// // Execute admin operation + /// } + /// return ValueTask.CompletedTask; + /// }, + /// state: null, + /// CancellationToken.None); + /// + /// public static class AnnotatingTaskSchedulerExtensions { + /// + /// Creates a task scheduler wrapper that attaches an operation name annotation to all enqueued tasks. + /// + /// The task scheduler to wrap with operation name annotation. + /// The name to associate with operations executed on this scheduler. + /// An that includes the operation name annotation in its extended state. + /// Thrown when is null, empty, or whitespace. + /// + /// + /// This method creates a wrapper scheduler that automatically attaches an + /// to all operations. The operation name can be retrieved by other extension methods in the TaskFlow pipeline, + /// such as error handlers, timeout handlers, and logging components. + /// + /// + /// The annotation is stored in the extended state and does not affect the core operation execution. + /// Multiple annotations can be layered by chaining extension methods. + /// + /// + /// + /// + /// ITaskScheduler scheduler = // ... obtain scheduler + /// + /// var namedScheduler = scheduler.WithOperationName("DataProcessing"); + /// + /// // The operation name will be available to error handlers, timeout handlers, etc. + /// var result = await namedScheduler + /// .WithTimeout(TimeSpan.FromSeconds(30)) + /// .OnError<TimeoutException>((sched, ex, name) => + /// Console.WriteLine($"Operation '{name?.OperationName}' timed out")) + /// .Enqueue(() => ProcessDataAsync()); + /// + /// public static ITaskScheduler WithOperationName(this ITaskScheduler taskScheduler, string operationName) { Argument.NotEmpty(operationName); @@ -12,6 +114,66 @@ public static ITaskScheduler WithOperationName(this ITaskScheduler taskScheduler return taskScheduler.WithExtendedState(new OperationNameAnnotation( operationName)); } + /// + /// Enqueues a task function that can access annotation data attached to the scheduler. + /// + /// The type of result produced by the task function. + /// The type of annotation to retrieve and pass to the task function. + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts state, annotation, and cancellation token parameters. + /// An optional state object that is passed to the . + /// A cancellation token that can be used to cancel the operation. + /// A representing the result of the enqueued task function. + /// Thrown when or is null. + /// Thrown if the scheduler has been disposed. + /// + /// + /// This method provides a convenient way to enqueue operations that need access to annotation metadata. + /// The method automatically extracts the first annotation of type + /// from the scheduler's extended state and passes it to the task function. + /// + /// + /// The annotation parameter will be null if: + /// + /// + /// No annotation of the specified type is found in the extended state + /// The scheduler has no extended state + /// The annotation type doesn't match any stored annotations + /// + /// + /// Annotation resolution works by unwrapping nested extended state objects and looking for the + /// first match of the specified annotation type. This allows for complex annotation hierarchies + /// created by chaining multiple extension methods. + /// + /// + /// + /// + /// // Setup scheduler with multiple annotations + /// var scheduler = taskFlow + /// .WithOperationName("UserOperation") + /// .WithExtendedState(new CustomAnnotation { Priority = "High" }); + /// + /// // Access operation name annotation + /// await scheduler.AnnotatedEnqueue<string, OperationNameAnnotation>( + /// (state, nameAnnotation, token) => { + /// var operationName = nameAnnotation?.OperationName ?? "Unknown"; + /// Console.WriteLine($"Executing: {operationName}"); + /// return ValueTask.FromResult("completed"); + /// }, + /// state: null, + /// CancellationToken.None); + /// + /// // Access custom annotation + /// await scheduler.AnnotatedEnqueue<void, CustomAnnotation>( + /// (state, customAnnotation, token) => { + /// var priority = customAnnotation?.Priority ?? "Normal"; + /// Console.WriteLine($"Priority: {priority}"); + /// return ValueTask.CompletedTask; + /// }, + /// state: null, + /// CancellationToken.None); + /// + /// public static Task AnnotatedEnqueue(this ITaskScheduler taskScheduler, Func> taskFunc, object? state, CancellationToken cancellationToken) where TAnnotation : class, IOperationAnnotation { diff --git a/TaskFlow/Extensions/CancelPreviousTaskSchedulerExtensions.cs b/TaskFlow/Extensions/CancelPreviousTaskSchedulerExtensions.cs index ac83e51..5406fda 100644 --- a/TaskFlow/Extensions/CancelPreviousTaskSchedulerExtensions.cs +++ b/TaskFlow/Extensions/CancelPreviousTaskSchedulerExtensions.cs @@ -3,8 +3,184 @@ namespace System.Threading.Tasks.Flow using System.Threading.Tasks.Flow.Annotations; using System.Threading.Tasks.Flow.Internal; + /// + /// Provides extension methods for to implement cancel-previous behavior where each new operation cancels all previously enqueued operations. + /// + /// + /// + /// This class implements a cancellation pattern where enqueueing a new operation automatically cancels + /// all previously enqueued operations that haven't completed yet. This is useful for scenarios where + /// only the most recent operation should be executed, and older operations become obsolete. + /// + /// + /// Key characteristics of cancel-previous behavior: + /// + /// + /// Each new operation cancels all pending operations + /// Only the most recently enqueued operation will typically complete + /// Already scheduled or running operations are cancelled but may complete if they don't check cancellation tokens promptly + /// + /// + /// Common use cases include: + /// + /// + /// Search suggestions - cancel previous search when user types new characters + /// Auto-save operations - cancel previous save when new save is triggered + /// Real-time data updates - cancel previous fetch when new data is requested + /// UI debouncing - cancel previous UI update when new update is needed + /// Progressive loading - cancel previous load operation when new load is started + /// + /// + /// The cancellation mechanism works by maintaining a collection of cancellation tokens and + /// cancelling all of them when a new operation is enqueued. This ensures that older operations + /// receive cancellation signals as soon as possible. + /// + /// + /// + /// Search suggestion scenario: + /// + /// ITaskScheduler scheduler = // ... obtain scheduler + /// var cancelPreviousScheduler = scheduler.CreateCancelPrevious(); + /// + /// // User types "a" - starts search + /// var search1 = cancelPreviousScheduler.Enqueue(() => SearchAsync("a")); + /// + /// // User types "ab" - cancels previous search and starts new one + /// var search2 = cancelPreviousScheduler.Enqueue(() => SearchAsync("ab")); + /// + /// // User types "abc" - cancels previous search and starts new one + /// var search3 = cancelPreviousScheduler.Enqueue(() => SearchAsync("abc")); + /// + /// // Only search3 will typically complete; search1 and search2 will be cancelled + /// var results = await search3; + /// + /// Auto-save with cancellation: + /// + /// var autoSaveScheduler = documentScheduler.CreateCancelPrevious(); + /// + /// void OnDocumentChanged() + /// { + /// // Cancel any pending save and start a new one + /// _ = autoSaveScheduler.Enqueue(async token => { + /// await Task.Delay(1000, token); // Debounce for 1 second + /// token.ThrowIfCancellationRequested(); + /// await SaveDocumentAsync(token); + /// }); + /// } + /// + /// // Multiple rapid changes will cancel previous saves + /// OnDocumentChanged(); // Save 1 started + /// OnDocumentChanged(); // Save 1 cancelled, Save 2 started + /// OnDocumentChanged(); // Save 2 cancelled, Save 3 started + /// // Only Save 3 will complete + /// + /// Real-time data updates: + /// + /// var dataUpdateScheduler = scheduler.CreateCancelPrevious(); + /// + /// async Task RefreshDataAsync() + /// { + /// try + /// { + /// var data = await dataUpdateScheduler.Enqueue(async token => { + /// // This will be cancelled if another refresh is triggered + /// var freshData = await FetchLatestDataAsync(token); + /// token.ThrowIfCancellationRequested(); + /// return freshData; + /// }); + /// + /// UpdateUI(data); + /// } + /// catch (OperationCanceledException) + /// { + /// // Previous operation was cancelled - this is expected + /// } + /// } + /// + /// public static class CancelPreviousTaskSchedulerExtensions { + /// + /// Creates a task scheduler wrapper that automatically cancels all previously enqueued operations when a new operation is enqueued. + /// + /// The base task scheduler to wrap with cancel-previous functionality. + /// An that implements cancel-previous behavior. + /// Thrown when is null. + /// + /// + /// This method creates a wrapper around the base task scheduler that maintains a collection of + /// cancellation tokens for all active operations. When a new operation is enqueued: + /// + /// + /// All existing cancellation tokens are triggered to cancel pending operations + /// A new cancellation token is allocated for the new operation + /// The new operation is linked with both its original cancellation token and the allocated token + /// + /// + /// The cancellation behavior is cooperative, meaning that operations must check their cancellation + /// tokens regularly to respond to cancellation requests. Operations that don't check cancellation + /// tokens may continue running even after being "cancelled". + /// + /// + /// Resource management is handled automatically - cancellation tokens are properly disposed + /// when operations complete, and the internal cancellation token allocator manages its resources. + /// + /// + /// The wrapper maintains all characteristics of the base scheduler (execution order, concurrency + /// behavior, etc.) while adding the cancel-previous functionality. + /// + /// + /// Performance considerations: + /// + /// + /// Each enqueue operation triggers cancellation of previous operations + /// Cancellation is typically very fast but involves some overhead + /// Memory usage is proportional to the number of concurrent operations + /// Cancelled operations may still consume resources until they check cancellation tokens + /// + /// + /// + /// + /// ITaskScheduler baseScheduler = new TaskFlow(); + /// var cancelPreviousScheduler = baseScheduler.CreateCancelPrevious(); + /// + /// // Enqueue multiple operations rapidly + /// var task1 = cancelPreviousScheduler.Enqueue(async token => { + /// await Task.Delay(1000, token); // Will likely be cancelled + /// return "Task 1"; + /// }); + /// + /// var task2 = cancelPreviousScheduler.Enqueue(async token => { + /// await Task.Delay(1000, token); // Will likely be cancelled + /// return "Task 2"; + /// }); + /// + /// var task3 = cancelPreviousScheduler.Enqueue(async token => { + /// await Task.Delay(1000, token); // Most likely to complete + /// return "Task 3"; + /// }); + /// + /// try + /// { + /// var result1 = await task1; // Will likely throw OperationCanceledException + /// } + /// catch (OperationCanceledException) + /// { + /// // Expected - task1 was cancelled by task2 + /// } + /// + /// try + /// { + /// var result2 = await task2; // Will likely throw OperationCanceledException + /// } + /// catch (OperationCanceledException) + /// { + /// // Expected - task2 was cancelled by task3 + /// } + /// + /// var result3 = await task3; // Should complete successfully with "Task 3" + /// + /// public static ITaskScheduler CreateCancelPrevious(this ITaskScheduler taskScheduler) { return new CancelPreviousTaskSchedulerWrapper(taskScheduler); diff --git a/TaskFlow/Extensions/CancellationScopeTaskSchedulerExtensions.cs b/TaskFlow/Extensions/CancellationScopeTaskSchedulerExtensions.cs index 964afd1..1ce38f8 100644 --- a/TaskFlow/Extensions/CancellationScopeTaskSchedulerExtensions.cs +++ b/TaskFlow/Extensions/CancellationScopeTaskSchedulerExtensions.cs @@ -2,8 +2,139 @@ namespace System.Threading.Tasks.Flow { using System.Threading.Tasks.Flow.Annotations; + /// + /// Provides extension methods for to create cancellation scopes that automatically link cancellation tokens. + /// + /// + /// + /// This class enables the creation of cancellation scopes around task schedulers, allowing for automatic + /// cancellation token linking. When a cancellation scope is created, all operations enqueued on the + /// resulting scheduler will have their cancellation tokens linked with the scope's cancellation token. + /// + /// + /// Key benefits of cancellation scopes: + /// + /// + /// Automatic cancellation propagation to all operations within the scope + /// Hierarchical cancellation - parent scope cancellation affects all child operations + /// Resource cleanup - operations can be cancelled when their containing scope is cancelled + /// Simplified cancellation management - no need to manually link tokens for each operation + /// + /// + /// This is particularly useful for: + /// + /// + /// Request-scoped cancellation in web applications + /// Component lifecycle management + /// Batch operation cancellation + /// Resource-bound operation groups + /// + /// + /// + /// Basic cancellation scope usage: + /// + /// using var cts = new CancellationTokenSource(); + /// ITaskScheduler scheduler = // ... obtain scheduler + /// + /// // Create a cancellation scope + /// var scopedScheduler = scheduler.CreateCancellationScope(cts.Token); + /// + /// // All operations on scopedScheduler will be cancelled if cts is cancelled + /// var task1 = scopedScheduler.Enqueue(() => LongRunningOperation1()); + /// var task2 = scopedScheduler.Enqueue(() => LongRunningOperation2()); + /// + /// // Cancel all operations in the scope + /// cts.Cancel(); + /// + /// // Both task1 and task2 will be cancelled + /// + /// Hierarchical cancellation with multiple scopes: + /// + /// using var parentCts = new CancellationTokenSource(); + /// using var childCts = new CancellationTokenSource(); + /// + /// ITaskScheduler baseScheduler = // ... obtain scheduler + /// + /// // Create nested cancellation scopes + /// var parentScope = baseScheduler.CreateCancellationScope(parentCts.Token); + /// var childScope = parentScope.CreateCancellationScope(childCts.Token); + /// + /// // Operations in child scope are affected by both parent and child cancellation + /// var childTask = childScope.Enqueue(() => SomeOperation()); + /// + /// // Cancelling either parentCts or childCts will cancel childTask + /// parentCts.Cancel(); // This will cancel childTask + /// + /// Request-scoped web application usage: + /// + /// public async Task ProcessRequestAsync(HttpContext context) + /// { + /// // Use request cancellation token to create scope + /// var requestScopedScheduler = _scheduler.CreateCancellationScope(context.RequestAborted); + /// + /// // All operations will be automatically cancelled if request is aborted + /// await requestScopedScheduler.Enqueue(() => ProcessDataAsync()); + /// await requestScopedScheduler.Enqueue(() => SaveResultsAsync()); + /// + /// // No need to manually pass context.RequestAborted to each operation + /// } + /// + /// public static class CancellationScopeTaskSchedulerExtensions { + /// + /// Creates a task scheduler that automatically links a scope cancellation token with all operation cancellation tokens. + /// + /// The base task scheduler to wrap with cancellation scope functionality. + /// The cancellation token that defines the scope. When this token is cancelled, all operations enqueued on the returned scheduler will be cancelled. + /// An that automatically links the scope cancellation token with operation cancellation tokens. + /// Thrown when is null. + /// + /// + /// This method creates a wrapper around the base task scheduler that intercepts all + /// + /// calls and creates a linked cancellation token combining the operation's cancellation token + /// with the scope cancellation token. + /// + /// + /// The linked token behavior follows these rules: + /// + /// + /// If either the scope token or operation token is cancelled, the linked token is cancelled + /// The linked token is automatically disposed after the operation completes + /// Cancellation can originate from either the scope or the individual operation + /// The first cancellation source to trigger will cancel the operation + /// + /// + /// The returned scheduler maintains all characteristics of the base scheduler (execution order, + /// concurrency behavior, etc.) while adding automatic cancellation scope functionality. + /// + /// + /// Multiple cancellation scopes can be nested by calling this method multiple times, + /// creating a hierarchy where any parent scope cancellation affects all child operations. + /// + /// + /// + /// + /// ITaskScheduler baseScheduler = // ... obtain scheduler + /// using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + /// + /// // Create scope that cancels all operations after 5 minutes + /// var scopedScheduler = baseScheduler.CreateCancellationScope(cts.Token); + /// + /// // This operation will be cancelled if either: + /// // 1. The individual operation token is cancelled, OR + /// // 2. The scope token (cts.Token) is cancelled after 5 minutes + /// using var operationCts = new CancellationTokenSource(); + /// var result = await scopedScheduler.Enqueue( + /// async token => { + /// // token is linked: will be cancelled by either operationCts or cts + /// await SomeAsyncOperation(token); + /// return "completed"; + /// }, + /// operationCts.Token); + /// + /// public static ITaskScheduler CreateCancellationScope(this ITaskScheduler taskScheduler, CancellationToken scopeCancellationToken) { return new CancellationScopeTaskSchedulerWrapper(taskScheduler, scopeCancellationToken); diff --git a/TaskFlow/Extensions/ExceptionTaskSchedulerExtensions.cs b/TaskFlow/Extensions/ExceptionTaskSchedulerExtensions.cs index 722e325..9fb28f6 100644 --- a/TaskFlow/Extensions/ExceptionTaskSchedulerExtensions.cs +++ b/TaskFlow/Extensions/ExceptionTaskSchedulerExtensions.cs @@ -3,14 +3,134 @@ namespace System.Threading.Tasks.Flow using System.Linq; using System.Threading.Tasks.Flow.Annotations; + /// + /// Provides extension methods for to add error handling capabilities with optional operation annotation support. + /// + /// + /// + /// This class enables sophisticated error handling patterns for task scheduler operations, including + /// exception filtering, type-specific handling, and integration with operation annotations for + /// contextual error processing. The error handlers are invoked when exceptions occur during + /// task execution, but they do not suppress the exceptions - they provide a way to observe and + /// respond to errors while maintaining normal exception propagation. + /// + /// + /// Key features of the error handling system: + /// + /// + /// Type-specific exception handling with generic constraints + /// Optional exception filtering to handle only specific error conditions + /// Integration with operation annotations for contextual error information + /// Non-suppressing behavior - exceptions continue to propagate after handling + /// Access to the scheduler instance for reactive error handling + /// + /// + /// Error handlers can be used for: + /// + /// + /// Logging errors with operation context + /// Sending error notifications or alerts + /// Recording metrics and telemetry + /// Triggering compensating actions + /// Enriching error information with operation metadata + /// + /// + /// The error handling system works by wrapping the base scheduler and intercepting exceptions + /// that occur during task execution. Multiple error handlers can be chained, and each handler + /// in the chain will be invoked when matching exceptions occur. + /// + /// + /// + /// Basic error handling with logging: + /// + /// ITaskScheduler scheduler = // ... obtain scheduler + /// + /// var errorHandlingScheduler = scheduler + /// .WithOperationName("DatabaseOperation") + /// .OnError<SqlException>((sched, ex, operationName) => { + /// _logger.LogError(ex, "Database error in operation {OperationName}", + /// operationName?.OperationName ?? "Unknown"); + /// }) + /// .OnError<TimeoutException>(ex => { + /// _metrics.IncrementCounter("operation.timeout"); + /// }); + /// + /// try + /// { + /// await errorHandlingScheduler.Enqueue(() => DatabaseQueryAsync()); + /// } + /// catch (SqlException ex) + /// { + /// // Exception was logged by error handler, then re-thrown + /// // Handle the exception or let it propagate further + /// } + /// + /// Custom annotation integration: + /// + /// public class UserContextAnnotation : IOperationAnnotation + /// { + /// public string UserId { get; set; } + /// public string Role { get; set; } + /// } + /// + /// var contextualErrorScheduler = scheduler + /// .WithExtendedState(new UserContextAnnotation { UserId = "user123", Role = "admin" }) + /// .OnError<SecurityException, UserContextAnnotation>((sched, ex, userContext) => { + /// _auditLog.LogSecurityViolation( + /// userId: userContext?.UserId ?? "unknown", + /// role: userContext?.Role ?? "unknown", + /// exception: ex); + /// }); + /// + /// Reactive error handling with compensating actions: + /// + /// var reactiveScheduler = scheduler + /// .WithOperationName("PaymentProcessing") + /// .OnError<PaymentFailedException>((errorScheduler, ex) => { + /// // Enqueue compensating action on the same scheduler + /// _ = errorScheduler.Enqueue(() => RefundPaymentAsync(ex.PaymentId)); + /// }) + /// .OnError<Exception>(ex => { + /// // Log all other exceptions + /// _logger.LogError(ex, "Unexpected error in payment processing"); + /// }); + /// + /// public static class ExceptionTaskSchedulerExtensions { + /// + /// Creates a task scheduler wrapper that handles exceptions of the specified type with access to the scheduler and operation name annotation. + /// + /// The type of exception to handle. Must derive from . + /// The task scheduler to wrap with error handling. + /// The action to execute when an exception of type occurs. Receives the scheduler instance, the exception, and the operation name annotation if available. + /// An optional predicate to filter which exceptions should be handled. If null, all exceptions of the specified type are handled. + /// An that handles exceptions according to the specified parameters. + /// Thrown when is null. + /// + /// This overload specifically provides access to in the error handler, + /// making it convenient for scenarios where operation names are used for error logging and diagnostics. + /// The error handler receives the scheduler instance, allowing for reactive error handling patterns. + /// public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction, Func? errorFilter = null) where TException : Exception { return new AnnotatedExceptionTaskSchedulerWrapper(taskScheduler, errorFilter ?? DefaultErrorFilter, (scheduler, exception, _) => errorAction(scheduler, exception)); } + /// + /// Creates a task scheduler wrapper that handles exceptions of the specified type with a simple error action. + /// + /// The type of exception to handle. Must derive from . + /// The task scheduler to wrap with error handling. + /// The action to execute when an exception of type occurs. Receives only the exception instance. + /// An optional predicate to filter which exceptions should be handled. If null, all exceptions of the specified type are handled. + /// An that handles exceptions according to the specified parameters. + /// Thrown when or is null. + /// + /// This is the simplest error handling overload, suitable for basic error logging or notification scenarios + /// where scheduler access and annotation context are not needed. + /// public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction, Func? errorFilter = null) where TException : Exception { @@ -19,22 +139,72 @@ public static ITaskScheduler OnError(this ITaskScheduler taskSchedul return new AnnotatedExceptionTaskSchedulerWrapper(taskScheduler, errorFilter ?? DefaultErrorFilter, (_, exception, _) => errorAction(exception)); } + /// + /// Creates a task scheduler wrapper that handles all exceptions with access to the scheduler instance. + /// + /// The task scheduler to wrap with error handling. + /// The action to execute when any exception occurs. Receives the scheduler instance and the exception. + /// An that handles all exceptions. + /// Thrown when is null. + /// + /// This overload handles all exception types derived from , making it suitable + /// for general error logging or fallback error handling scenarios. + /// public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction) { return OnError(taskScheduler, errorAction); } + /// + /// Creates a task scheduler wrapper that handles all exceptions with a simple error action. + /// + /// The task scheduler to wrap with error handling. + /// The action to execute when any exception occurs. Receives only the exception instance. + /// An that handles all exceptions. + /// Thrown when is null. + /// + /// This is the most general error handling overload, suitable for basic error logging scenarios + /// where specific exception types and operation context are not important. + /// public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction) { return OnError(taskScheduler, errorAction); } + /// + /// Creates a task scheduler wrapper that handles exceptions of the specified type with access to operation name annotations. + /// + /// The type of exception to handle. Must derive from . + /// The task scheduler to wrap with error handling. + /// The action to execute when an exception occurs. Receives the scheduler, exception, and operation name annotation. + /// An optional predicate to filter which exceptions should be handled. If null, all exceptions of the specified type are handled. + /// An that handles exceptions with operation name context. + /// Thrown when is null. + /// + /// This overload explicitly provides access, making it ideal for + /// scenarios where operation names are consistently used and needed for error context. + /// public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction, Func? errorFilter = null) where TException : Exception { return new AnnotatedExceptionTaskSchedulerWrapper(taskScheduler, errorFilter ?? DefaultErrorFilter, errorAction); } + /// + /// Creates a task scheduler wrapper that handles exceptions of the specified type with access to custom operation annotations. + /// + /// The type of exception to handle. Must derive from . + /// The type of operation annotation to provide to the error handler. Must implement . + /// The task scheduler to wrap with error handling. + /// The action to execute when an exception occurs. Receives the scheduler, exception, and custom annotation. + /// An optional predicate to filter which exceptions should be handled. If null, all exceptions of the specified type are handled. + /// An that handles exceptions with custom annotation context. + /// Thrown when is null. + /// + /// This is the most flexible error handling overload, allowing access to any custom annotation type + /// that implements . This enables rich contextual error handling + /// with application-specific metadata. + /// public static ITaskScheduler OnError(this ITaskScheduler taskScheduler, Action errorAction, Func? errorFilter = null) where TException : Exception where TAnnotation : IOperationAnnotation diff --git a/TaskFlow/Extensions/IOperationAnnotation.cs b/TaskFlow/Extensions/IOperationAnnotation.cs index 1ca03be..e05d48d 100644 --- a/TaskFlow/Extensions/IOperationAnnotation.cs +++ b/TaskFlow/Extensions/IOperationAnnotation.cs @@ -1,5 +1,71 @@ namespace System.Threading.Tasks.Flow { + /// + /// Defines a marker interface for operation annotations that can be attached to task scheduler operations. + /// + /// + /// + /// The interface serves as a marker interface for objects that + /// provide metadata about task scheduler operations. Annotations implementing this interface can be + /// attached to task schedulers through extension methods and retrieved by other components in the + /// TaskFlow pipeline. + /// + /// + /// This interface enables type-safe annotation systems where different components can: + /// + /// + /// Store operation-specific metadata without affecting core execution + /// Retrieve relevant annotations by type for processing + /// Create custom annotation types for specific use cases + /// Support cross-cutting concerns like logging, monitoring, and error handling + /// + /// + /// Common annotation implementations include: + /// + /// + /// - for operation naming and identification + /// Custom user context annotations - for authorization and personalization + /// Correlation ID annotations - for distributed tracing + /// Priority annotations - for operation prioritization + /// + /// + /// + /// Creating a custom annotation: + /// + /// public class UserContextAnnotation : IOperationAnnotation + /// { + /// public string UserId { get; set; } + /// public string Role { get; set; } + /// public string TenantId { get; set; } + /// } + /// + /// public class CorrelationAnnotation : IOperationAnnotation + /// { + /// public string CorrelationId { get; set; } + /// public string RequestId { get; set; } + /// } + /// + /// Using annotations with task schedulers: + /// + /// // Attach multiple annotations + /// var scheduler = taskFlow + /// .WithOperationName("ProcessOrder") + /// .WithExtendedState(new UserContextAnnotation { UserId = "user123", Role = "customer" }) + /// .WithExtendedState(new CorrelationAnnotation { CorrelationId = Guid.NewGuid().ToString() }); + /// + /// // Access annotations in operations + /// await scheduler.AnnotatedEnqueue<void, UserContextAnnotation>( + /// (state, userContext, token) => { + /// // Use user context for authorization + /// if (userContext?.Role == "admin") { + /// // Execute admin-specific logic + /// } + /// return ValueTask.CompletedTask; + /// }, + /// state: null, + /// CancellationToken.None); + /// + /// public interface IOperationAnnotation { } diff --git a/TaskFlow/Extensions/OperationNameAnnotation.cs b/TaskFlow/Extensions/OperationNameAnnotation.cs index ee58612..908f660 100644 --- a/TaskFlow/Extensions/OperationNameAnnotation.cs +++ b/TaskFlow/Extensions/OperationNameAnnotation.cs @@ -1,5 +1,74 @@ namespace System.Threading.Tasks.Flow { + /// + /// Provides an operation annotation that stores a name identifier for task scheduler operations. + /// + /// + /// + /// The class implements + /// to provide a simple way to name and identify operations within the TaskFlow system. This annotation + /// is commonly used for logging, debugging, error reporting, and monitoring purposes. + /// + /// + /// The operation name annotation is typically created through the + /// + /// extension method, which automatically wraps the scheduler with this annotation. + /// + /// + /// Key characteristics: + /// + /// + /// Immutable - the operation name cannot be changed after creation + /// Lightweight - stores only the operation name string + /// Thread-safe - can be safely accessed from multiple threads + /// Integrated - automatically used by other TaskFlow extension methods + /// + /// + /// This annotation is automatically recognized by several TaskFlow extension methods: + /// + /// + /// Error handling extensions include the operation name in error context + /// Timeout extensions include the operation name in timeout exception messages + /// Logging extensions can extract the operation name for structured logging + /// + /// + /// + /// Basic usage with operation naming: + /// + /// ITaskScheduler scheduler = // ... obtain scheduler + /// + /// // Create named scheduler - this creates an OperationNameAnnotation internally + /// var namedScheduler = scheduler.WithOperationName("ProcessPayment"); + /// + /// // The operation name is available to error handlers + /// var result = await namedScheduler + /// .OnError<InvalidOperationException>((sched, ex, nameAnnotation) => + /// Console.WriteLine($"Error in operation '{nameAnnotation?.OperationName}': {ex.Message}")) + /// .Enqueue(() => ProcessPaymentAsync()); + /// + /// Accessing the annotation directly: + /// + /// await namedScheduler.AnnotatedEnqueue<string, OperationNameAnnotation>( + /// (state, nameAnnotation, token) => { + /// var operationName = nameAnnotation?.OperationName ?? "Unknown"; + /// Console.WriteLine($"Starting operation: {operationName}"); + /// + /// // Perform the actual work + /// return ValueTask.FromResult("completed"); + /// }, + /// state: null, + /// CancellationToken.None); + /// + /// Integration with timeout handling: + /// + /// var result = await scheduler + /// .WithOperationName("DatabaseQuery") + /// .WithTimeout(TimeSpan.FromSeconds(30)) + /// .Enqueue(() => QueryDatabaseAsync()); + /// + /// // If timeout occurs, the exception message will include "Operation `DatabaseQuery` has timed out in 00:00:30" + /// + /// public sealed class OperationNameAnnotation : IOperationAnnotation { internal OperationNameAnnotation(string operationName) @@ -7,6 +76,25 @@ internal OperationNameAnnotation(string operationName) OperationName = operationName; } + /// + /// Gets the name associated with the operation. + /// + /// A string representing the operation name that was specified when the annotation was created. + /// + /// + /// The operation name is immutable and cannot be changed after the annotation is created. + /// This ensures consistent identification of operations throughout their execution lifecycle. + /// + /// + /// The operation name is used by various TaskFlow extension methods for: + /// + /// + /// Including operation context in error messages and exceptions + /// Providing operation identification in timeout scenarios + /// Supporting structured logging and monitoring + /// Enabling operation-specific debugging and diagnostics + /// + /// public string OperationName { get; } } } \ No newline at end of file diff --git a/TaskFlow/Extensions/OperationThrottledException.cs b/TaskFlow/Extensions/OperationThrottledException.cs index 1166d3b..ffcd58e 100644 --- a/TaskFlow/Extensions/OperationThrottledException.cs +++ b/TaskFlow/Extensions/OperationThrottledException.cs @@ -2,6 +2,80 @@ namespace System.Threading.Tasks.Flow { using System.Runtime.Serialization; + /// + /// Represents an exception that is thrown when an operation is throttled or rejected due to timing constraints. + /// + /// + /// + /// The is thrown by throttling mechanisms in the TaskFlow system + /// when operations are rejected based on timing or rate limiting policies. This exception derives from + /// because throttled operations are effectively cancelled due + /// to policy constraints rather than explicit user cancellation. + /// + /// + /// This exception is commonly thrown by: + /// + /// + /// Debounce mechanisms when operations are enqueued too quickly + /// Rate limiting systems when operation frequency exceeds allowed thresholds + /// Throttling wrappers that enforce minimum intervals between operations + /// Backpressure systems that reject operations under high load + /// + /// + /// Unlike regular , this exception specifically indicates that + /// the operation was cancelled due to throttling policies rather than explicit cancellation requests. + /// This distinction allows applications to handle throttling scenarios differently from user-initiated + /// cancellations. + /// + /// + /// The exception is serializable and supports the standard .NET exception patterns for proper + /// cross-AppDomain and serialization scenarios. + /// + /// + /// + /// Handling throttled operations in a debounce scenario: + /// + /// var debouncedScheduler = scheduler.WithDebounce(TimeSpan.FromSeconds(1)); + /// + /// try + /// { + /// // These operations are enqueued too quickly + /// await debouncedScheduler.Enqueue(() => SaveDocumentAsync()); + /// await debouncedScheduler.Enqueue(() => SaveDocumentAsync()); // Too soon - will throw + /// } + /// catch (OperationThrottledException ex) + /// { + /// // Handle throttling - maybe show user feedback or retry later + /// _logger.LogInformation("Save operation throttled: {Message}", ex.Message); + /// + /// // Could implement retry logic + /// await Task.Delay(TimeSpan.FromSeconds(1)); + /// await debouncedScheduler.Enqueue(() => SaveDocumentAsync()); + /// } + /// catch (OperationCanceledException ex) when (ex is not OperationThrottledException) + /// { + /// // Handle explicit cancellation differently + /// _logger.LogInformation("Save operation was cancelled by user"); + /// } + /// + /// Differentiating throttling from other cancellations: + /// + /// try + /// { + /// await throttledScheduler.Enqueue(() => SomeOperationAsync()); + /// } + /// catch (OperationThrottledException) + /// { + /// // Specific handling for throttling + /// ShowUserMessage("Operation rate limited. Please wait and try again."); + /// } + /// catch (OperationCanceledException) + /// { + /// // Handle other cancellation scenarios + /// ShowUserMessage("Operation was cancelled."); + /// } + /// + /// [Serializable] public sealed class OperationThrottledException : OperationCanceledException { diff --git a/TaskFlow/Extensions/ThrottlingTaskSchedulerExtensions.cs b/TaskFlow/Extensions/ThrottlingTaskSchedulerExtensions.cs index 15be0bd..66a0d62 100644 --- a/TaskFlow/Extensions/ThrottlingTaskSchedulerExtensions.cs +++ b/TaskFlow/Extensions/ThrottlingTaskSchedulerExtensions.cs @@ -2,8 +2,186 @@ namespace System.Threading.Tasks.Flow { using System.Threading.Tasks.Flow.Annotations; + /// + /// Provides extension methods for to add throttling and debouncing capabilities. + /// + /// + /// + /// This class implements throttling mechanisms that control the rate at which operations can be executed + /// on a task scheduler. The primary implementation is a debounce pattern that enforces a minimum time + /// interval between successful operation executions. + /// + /// + /// Throttling behaviors provided: + /// + /// + /// Debouncing - Enforces a minimum interval between operations, rejecting operations that are enqueued too soon after the previous execution + /// Time-based throttling - Uses configurable time providers for precise timing control + /// Exception-based rejection - Throws when operations are throttled + /// Thread-safe operation - Multiple threads can safely enqueue operations with proper synchronization + /// + /// + /// Common use cases for throttling include: + /// + /// + /// Auto-save functionality with debouncing to prevent excessive file writes + /// Search suggestion throttling to reduce server requests + /// Rate limiting for external API calls + /// UI update throttling to prevent excessive rendering + /// Resource protection by limiting operation frequency + /// + /// + /// The throttling implementation tracks the timestamp of the last successful operation execution + /// and compares it with the current time when new operations are enqueued. If the elapsed time + /// is less than the configured interval, the operation is rejected with an . + /// + /// + /// + /// Basic debounce implementation for auto-save: + /// + /// ITaskScheduler scheduler = // ... obtain scheduler + /// var debouncedScheduler = scheduler.WithDebounce(TimeSpan.FromSeconds(2)); + /// + /// // Simulate rapid document changes + /// for (int i = 0; i < 5; i++) + /// { + /// try + /// { + /// await debouncedScheduler.Enqueue(() => SaveDocumentAsync()); + /// Console.WriteLine($"Save {i + 1} completed"); + /// } + /// catch (OperationThrottledException ex) + /// { + /// Console.WriteLine($"Save {i + 1} throttled: {ex.Message}"); + /// } + /// + /// await Task.Delay(500); // Wait 500ms between attempts + /// } + /// // Only the first save and saves after 2+ second intervals will succeed + /// + /// Search debouncing with error handling: + /// + /// var searchScheduler = scheduler.WithDebounce(TimeSpan.FromMilliseconds(300)); + /// + /// async Task HandleSearchInput(string query) + /// { + /// try + /// { + /// var results = await searchScheduler.Enqueue(async () => { + /// return await SearchApiAsync(query); + /// }); + /// + /// DisplaySearchResults(results); + /// } + /// catch (OperationThrottledException) + /// { + /// // Search was throttled - this is expected behavior + /// // No action needed as the user is still typing + /// } + /// } + /// + /// // User typing "hello" quickly: + /// await HandleSearchInput("h"); // Executes + /// await HandleSearchInput("he"); // Throttled + /// await HandleSearchInput("hel"); // Throttled + /// await HandleSearchInput("hell"); // Throttled + /// await HandleSearchInput("hello"); // Throttled + /// // Only "h" search executes; if user pauses > 300ms, next search will execute + /// + /// Custom time provider for testing: + /// + /// // Using FakeTimeProvider for unit testing + /// var fakeTimeProvider = new FakeTimeProvider(); + /// var testScheduler = scheduler.WithDebounce(TimeSpan.FromMinutes(5), fakeTimeProvider); + /// + /// // First operation should succeed + /// await testScheduler.Enqueue(() => SomeOperation()); + /// + /// // Advance time by 4 minutes - next operation should be throttled + /// fakeTimeProvider.Advance(TimeSpan.FromMinutes(4)); + /// await Assert.ThrowsAsync<OperationThrottledException>(() => + /// testScheduler.Enqueue(() => SomeOperation())); + /// + /// // Advance time by 2 more minutes (6 minutes total) - next operation should succeed + /// fakeTimeProvider.Advance(TimeSpan.FromMinutes(2)); + /// await testScheduler.Enqueue(() => SomeOperation()); // Should succeed + /// + /// public static class ThrottlingTaskSchedulerExtensions { + /// + /// Creates a task scheduler wrapper that implements debouncing by enforcing a minimum time interval between operation executions. + /// + /// The base task scheduler to wrap with debouncing functionality. + /// The minimum time interval that must elapse between successful operation executions. + /// The time provider to use for timing measurements. If null, is used. + /// An that enforces the debounce interval between operations. + /// Thrown when is null. + /// Thrown when is less than or equal to . + /// + /// + /// This method creates a wrapper that tracks the timestamp of the last successful operation execution. + /// When a new operation is enqueued, the wrapper checks if the specified interval has elapsed since + /// the last execution. If not enough time has passed, the operation is immediately rejected with an + /// . + /// + /// + /// Debounce behavior characteristics: + /// + /// + /// First operation - Always allowed to execute immediately + /// Subsequent operations - Only allowed if the interval has elapsed since the last successful execution + /// Failed operations - Do not count toward the interval timing (only successful completions are tracked) + /// Cancelled operations - Do not count toward the interval timing + /// Thread safety - Multiple threads can safely enqueue operations with proper synchronization + /// + /// + /// The time provider parameter allows for custom timing implementations, which is particularly useful + /// for unit testing scenarios where time needs to be controlled or accelerated. When null is + /// provided, the system time provider is used for production scenarios. + /// + /// + /// The wrapper maintains all characteristics of the base scheduler (execution order, concurrency + /// behavior, etc.) while adding the debouncing functionality. Operations that pass the debounce + /// check are forwarded to the base scheduler unchanged. + /// + /// + /// Performance considerations: + /// + /// + /// Each enqueue operation requires a timestamp comparison (very fast) + /// Thread synchronization overhead for the timestamp check + /// Rejected operations fail immediately without queuing overhead + /// Memory overhead is minimal (single timestamp and lock object) + /// + /// + /// + /// + /// ITaskScheduler baseScheduler = new TaskFlow(); + /// + /// // Create debounced scheduler with 1-second interval + /// var debouncedScheduler = baseScheduler.WithDebounce(TimeSpan.FromSeconds(1)); + /// + /// // First operation executes immediately + /// await debouncedScheduler.Enqueue(() => Console.WriteLine("Operation 1")); + /// + /// try + /// { + /// // This will be throttled since less than 1 second has passed + /// await debouncedScheduler.Enqueue(() => Console.WriteLine("Operation 2")); + /// } + /// catch (OperationThrottledException ex) + /// { + /// Console.WriteLine($"Throttled: {ex.Message}"); + /// } + /// + /// // Wait for interval to pass + /// await Task.Delay(TimeSpan.FromSeconds(1.1)); + /// + /// // This operation will execute since interval has elapsed + /// await debouncedScheduler.Enqueue(() => Console.WriteLine("Operation 3")); + /// + /// public static ITaskScheduler WithDebounce(this ITaskScheduler taskScheduler, TimeSpan interval, TimeProvider? timeProvider = null) { return new DebounceTaskSchedulerWrapper(taskScheduler, timeProvider ?? TimeProvider.System, interval); diff --git a/TaskFlow/Extensions/TimeoutTaskSchedulerExtensions.cs b/TaskFlow/Extensions/TimeoutTaskSchedulerExtensions.cs index a5c89c1..6f3bb6c 100644 --- a/TaskFlow/Extensions/TimeoutTaskSchedulerExtensions.cs +++ b/TaskFlow/Extensions/TimeoutTaskSchedulerExtensions.cs @@ -4,8 +4,201 @@ namespace System.Threading.Tasks.Flow using System.Linq; using System.Threading.Tasks.Flow.Annotations; + /// + /// Provides extension methods for to add timeout capabilities to task operations. + /// + /// + /// + /// This class implements timeout functionality for task scheduler operations, automatically cancelling + /// operations that exceed a specified time limit. The timeout mechanism works by racing the actual + /// operation against a delay task, cancelling whichever doesn't complete first. + /// + /// + /// Key features of the timeout system: + /// + /// + /// Automatic cancellation - Operations are cancelled if they exceed the timeout duration + /// Operation name integration - Timeout exceptions include operation names when available + /// Resource management - Properly cancels and cleans up both timeout and operation tasks + /// Exception clarity - Provides clear timeout exception messages with contextual information + /// + /// + /// The timeout implementation uses a race condition between two tasks: + /// + /// + /// The actual operation task + /// A timeout delay task + /// + /// + /// Whichever task completes first determines the outcome. If the timeout task completes first, + /// a is thrown and the operation task is cancelled. If the operation + /// completes first, the timeout task is cancelled and the operation result is returned. + /// + /// + /// Common use cases include: + /// + /// + /// Protecting against hanging operations in external service calls + /// Enforcing SLA requirements for operation completion times + /// Preventing resource exhaustion from long-running operations + /// Implementing circuit breaker patterns with time-based failures + /// Adding timeout protection to legacy or unreliable code + /// + /// + /// + /// Basic timeout usage: + /// + /// ITaskScheduler scheduler = // ... obtain scheduler + /// var timeoutScheduler = scheduler.WithTimeout(TimeSpan.FromSeconds(30)); + /// + /// try + /// { + /// var result = await timeoutScheduler.Enqueue(async () => { + /// // This operation has 30 seconds to complete + /// await SomeLongRunningOperationAsync(); + /// return "completed"; + /// }); + /// + /// Console.WriteLine($"Operation completed: {result}"); + /// } + /// catch (TimeoutException ex) + /// { + /// Console.WriteLine($"Operation timed out: {ex.Message}"); + /// } + /// + /// Timeout with operation naming for better error messages: + /// + /// var namedTimeoutScheduler = scheduler + /// .WithOperationName("DatabaseQuery") + /// .WithTimeout(TimeSpan.FromSeconds(15)); + /// + /// try + /// { + /// var data = await namedTimeoutScheduler.Enqueue(() => QueryDatabaseAsync()); + /// } + /// catch (TimeoutException ex) + /// { + /// // Exception message will include: "Operation `DatabaseQuery` has timed out in 00:00:15" + /// _logger.LogError(ex, "Database query exceeded timeout"); + /// } + /// + /// Chaining timeouts with other extensions: + /// + /// var robustScheduler = scheduler + /// .WithOperationName("ExternalApiCall") + /// .WithTimeout(TimeSpan.FromSeconds(10)) + /// .OnError<TimeoutException>((sched, ex, name) => { + /// _metrics.IncrementCounter("api.timeout", + /// new { operation = name?.OperationName ?? "unknown" }); + /// }) + /// .OnError<HttpRequestException>(ex => { + /// _logger.LogWarning(ex, "HTTP error in API call"); + /// }); + /// + /// // This operation has comprehensive error handling and timeout protection + /// var result = await robustScheduler.Enqueue(() => CallExternalApiAsync()); + /// + /// Different timeout scenarios: + /// + /// // Short timeout for quick operations + /// var quickScheduler = scheduler.WithTimeout(TimeSpan.FromSeconds(5)); + /// + /// // Longer timeout for complex operations + /// var complexScheduler = scheduler.WithTimeout(TimeSpan.FromMinutes(2)); + /// + /// // Infinite timeout (effectively disables timeout) + /// var infiniteScheduler = scheduler.WithTimeout(Timeout.InfiniteTimeSpan); + /// + /// // Very precise timeout + /// var preciseScheduler = scheduler.WithTimeout(TimeSpan.FromMilliseconds(500)); + /// + /// public static class TimeoutTaskSchedulerExtensions { + /// + /// Creates a task scheduler wrapper that automatically cancels operations that exceed the specified timeout duration. + /// + /// The base task scheduler to wrap with timeout functionality. + /// The maximum time to allow for operation completion. Must be greater than and not exceed milliseconds, or be equal to . + /// An that enforces the specified timeout on all operations. + /// Thrown when is null. + /// Thrown when is not a valid timeout value. + /// + /// + /// This method creates a wrapper that implements a timeout mechanism by racing the actual operation + /// against a delay task. The implementation uses + /// to ensure that when one task completes, the other is properly cancelled and cleaned up. + /// + /// + /// Timeout behavior characteristics: + /// + /// + /// Race condition - The first task to complete (operation or timeout) determines the outcome + /// Automatic cancellation - When timeout occurs, the operation task receives a cancellation signal + /// Resource cleanup - Both tasks are properly cancelled and disposed regardless of which completes first + /// Exception handling - Timeout results in a with descriptive message + /// Cancellation propagation - Original cancellation tokens are respected in addition to timeout cancellation + /// + /// + /// The timeout value validation ensures: + /// + /// + /// Positive timeout values are within the valid range for + /// Zero or negative values are rejected as invalid + /// + /// + /// Exception message formatting includes operation context when available: + /// + /// + /// If an is present, the message includes the operation name + /// If no operation name is available, a generic timeout message is used + /// The timeout duration is always included in the message for diagnostics + /// + /// + /// The wrapper maintains all characteristics of the base scheduler (execution order, concurrency + /// behavior, etc.) while adding timeout protection. Operations that complete within the timeout + /// are returned normally with no additional overhead. + /// + /// + /// Performance considerations: + /// + /// + /// Each operation creates an additional timeout task that runs concurrently + /// Task cancellation and cleanup overhead when timeout occurs + /// Minimal overhead for operations that complete within timeout + /// Memory overhead proportional to the number of concurrent timeout operations + /// + /// + /// + /// + /// ITaskScheduler baseScheduler = new TaskFlow(); + /// + /// // Create scheduler with 30-second timeout + /// var timeoutScheduler = baseScheduler.WithTimeout(TimeSpan.FromSeconds(30)); + /// + /// try + /// { + /// // This operation must complete within 30 seconds + /// var result = await timeoutScheduler.Enqueue(async token => { + /// // Long-running operation that respects cancellation + /// for (int i = 0; i < 100; i++) + /// { + /// token.ThrowIfCancellationRequested(); // Check for timeout cancellation + /// await Task.Delay(500, token); // Simulate work + /// } + /// return "completed"; + /// }); + /// } + /// catch (TimeoutException ex) + /// { + /// Console.WriteLine($"Operation exceeded 30 second timeout: {ex.Message}"); + /// } + /// catch (OperationCanceledException ex) when (ex is not TimeoutException) + /// { + /// Console.WriteLine("Operation was cancelled for other reasons"); + /// } + /// + /// public static ITaskScheduler WithTimeout(this ITaskScheduler taskScheduler, TimeSpan timeout) { return new TimeoutTaskSchedulerWrapper(taskScheduler, timeout); diff --git a/TaskFlow/TaskFlowSchedulerAdapter.cs b/TaskFlow/TaskFlowSchedulerAdapter.cs index 33cf5e2..e2d77c5 100644 --- a/TaskFlow/TaskFlowSchedulerAdapter.cs +++ b/TaskFlow/TaskFlowSchedulerAdapter.cs @@ -2,10 +2,66 @@ namespace System.Threading.Tasks.Flow { using System.Threading.Tasks.Flow.Annotations; + /// + /// Provides an adapter that allows .NET Framework's to be used as an . + /// + /// + /// + /// The class bridges the gap between the standard .NET + /// and the TaskFlow interface. This enables + /// existing .NET task schedulers to be used within the TaskFlow ecosystem. + /// + /// + /// This adapter is particularly useful for: + /// + /// + /// Integrating with existing code that uses standard .NET implementations + /// Leveraging built-in schedulers like or custom schedulers + /// Migrating from standard task scheduling to the TaskFlow model incrementally + /// Using specialized schedulers (UI thread schedulers, limited concurrency schedulers, etc.) + /// + /// + /// The adapter uses + /// to schedule tasks on the underlying , ensuring that all tasks are executed + /// according to the scheduler's specific behavior and constraints. + /// + /// + /// + /// Using the adapter with TaskScheduler.Default: + /// + /// // Wrap the default task scheduler + /// ITaskScheduler taskFlowScheduler = new TaskFlowSchedulerAdapter(TaskScheduler.Default); + /// + /// // Now you can use it with TaskFlow extension methods + /// var result = await taskFlowScheduler.Enqueue(async () => { + /// await SomeAsyncOperation(); + /// return "completed"; + /// }); + /// + /// Using with a custom task scheduler: + /// + /// // Create a limited concurrency scheduler + /// var limitedScheduler = new LimitedConcurrencyLevelTaskScheduler(2); + /// var adapter = new TaskFlowSchedulerAdapter(limitedScheduler); + /// + /// // Tasks will be limited to 2 concurrent executions + /// await adapter.Enqueue(() => DoWork()); + /// + /// public sealed class TaskFlowSchedulerAdapter : ITaskScheduler { private readonly TaskScheduler _taskScheduler; + /// + /// Initializes a new instance of the class with the specified task scheduler. + /// + /// The .NET to wrap and adapt. + /// Thrown when is null. + /// + /// The adapter will delegate all task scheduling operations to the provided , + + /// maintaining the original scheduler's execution characteristics and constraints. + /// public TaskFlowSchedulerAdapter(TaskScheduler taskScheduler) { Argument.NotNull(taskScheduler); @@ -13,6 +69,39 @@ public TaskFlowSchedulerAdapter(TaskScheduler taskScheduler) _taskScheduler = taskScheduler; } + /// + /// Enqueues a task function for execution on the underlying . + /// + /// The type of result produced by the task function. + /// The function to execute that accepts state and a cancellation token and returns a . + /// An optional state object that is passed to the . + /// A cancellation token that can be used to cancel the operation. + /// A representing the result of the enqueued task function. + /// Thrown when is null. + /// Thrown if the underlying task scheduler rejects the task. + /// Thrown if the underlying task scheduler has been disposed. + /// + /// + /// This method uses + /// to schedule the task function on the underlying . The function is executed + /// according to the scheduler's specific behavior and constraints. + /// + /// + /// The method performs a double-await pattern: + /// + /// + /// First await on the task returned by + /// Second await on the returned by the task function + /// + /// + /// This ensures proper exception propagation and maintains the asynchronous nature of the task function + /// while respecting the underlying scheduler's execution model. + /// + /// + /// The cancellation token is honored by both the task scheduling mechanism and passed through to + /// the task function for internal cancellation handling. + /// + /// public async Task Enqueue(Func> taskFunc, object? state, CancellationToken cancellationToken) { Argument.NotNull(taskFunc); diff --git a/TaskFlow/TaskSchedulerEnqueueExtensions.cs b/TaskFlow/TaskSchedulerEnqueueExtensions.cs index e0099f7..336d2be 100644 --- a/TaskFlow/TaskSchedulerEnqueueExtensions.cs +++ b/TaskFlow/TaskSchedulerEnqueueExtensions.cs @@ -5,8 +5,64 @@ namespace System.Threading.Tasks.Flow using System.Threading.Tasks.Flow.Annotations; using System.Threading.Tasks.Flow.Internal; + /// + /// Provides extension methods for to simplify task enqueueing operations. + /// + /// + /// + /// This class provides a comprehensive set of extension methods that allow scheduling various types of operations + /// on an without having to manually adapt function signatures and handle state passing. + /// The methods handle conversion between different task types (, , + /// synchronous functions, and actions) and provide convenient overloads for common scenarios. + /// + /// + /// All extension methods perform argument validation and ensure proper exception propagation. + /// Methods that don't specify a will use . + /// + /// + /// The extension methods support: + /// + /// + /// Functions returning and + /// Functions returning and + /// Synchronous functions and actions + /// Functions with and without state parameters + /// Operations with and without cancellation token support + /// + /// + /// + /// + /// ITaskScheduler scheduler = // ... obtain scheduler + /// + /// // Enqueue a function that returns a value + /// var result = await scheduler.Enqueue(() => 42); + /// + /// // Enqueue an async operation with cancellation + /// var asyncResult = await scheduler.Enqueue(async token => { + /// await SomeAsyncOperation(token); + /// return "completed"; + /// }, cancellationToken); + /// + /// // Enqueue an action + /// await scheduler.Enqueue(() => Console.WriteLine("Hello World")); + /// + /// public static class TaskSchedulerEnqueueExtensions { + /// + /// Enqueues a function that returns a for execution. + /// + /// The type of result produced by the function. + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts a cancellation token and returns a . + /// A cancellation token that can be used to cancel the operation. + /// A representing the result of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method adapts a function that takes only a cancellation token to the scheduler's signature + /// that requires a state parameter by passing null as the state. + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func> taskFunc, CancellationToken cancellationToken) { Argument.NotNull(taskScheduler); @@ -19,6 +75,20 @@ ValueTask TaskFunc(object? state, CancellationToken token) } } + /// + /// Enqueues a function that returns a for execution. + /// + /// The type of result produced by the function. + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts a cancellation token and returns a . + /// A cancellation token that can be used to cancel the operation. + /// A representing the result of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method wraps the returned by the function in a + /// to match the scheduler's expected signature. + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func> taskFunc, CancellationToken cancellationToken) { Argument.NotNull(taskScheduler); @@ -31,6 +101,22 @@ ValueTask TaskFunc(object? state, CancellationToken token) } } + /// + /// Enqueues a function that returns a for execution with state. + /// + /// The type of result produced by the function. + /// The type of state passed to the function. + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts state and a cancellation token and returns a . + /// The state object to pass to the function. + /// A cancellation token that can be used to cancel the operation. + /// A representing the result of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method adapts a strongly-typed state parameter function to the scheduler's object-based state signature + /// by performing the necessary type casting. + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func> taskFunc, TState state, CancellationToken cancellationToken) { Argument.NotNull(taskScheduler); @@ -43,6 +129,21 @@ ValueTask TaskFunc(object? s, CancellationToken token) } } + /// + /// Enqueues a function that returns a for execution with state. + /// + /// The type of state passed to the function. + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts state and a cancellation token and returns a . + /// The state object to pass to the function. + /// A cancellation token that can be used to cancel the operation. + /// A representing the completion of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method wraps the non-generic in a with a + /// result to match the scheduler's signature requirement. + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func taskFunc, TState state, CancellationToken cancellationToken) { Argument.NotNull(taskScheduler); @@ -56,6 +157,22 @@ async ValueTask TaskFunc(object? s, CancellationToken token) } } + /// + /// Enqueues a function that returns a for execution with state. + /// + /// The type of result produced by the function. + /// The type of state passed to the function. + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts state and a cancellation token and returns a . + /// The state object to pass to the function. + /// A cancellation token that can be used to cancel the operation. + /// A representing the result of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method wraps the returned by the function in a + /// and adapts the strongly-typed state parameter to the scheduler's object-based state signature. + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func> taskFunc, TState state, CancellationToken cancellationToken) { Argument.NotNull(taskScheduler); @@ -68,6 +185,21 @@ ValueTask TaskFunc(object? s, CancellationToken token) } } + /// + /// Enqueues a function that returns a for execution with state. + /// + /// The type of state passed to the function. + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts state and a cancellation token and returns a . + /// The state object to pass to the function. + /// A cancellation token that can be used to cancel the operation. + /// A representing the completion of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method wraps the non-generic in a to match the scheduler's + /// signature and casts the state parameter appropriately. + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func taskFunc, TState state, CancellationToken cancellationToken) { Argument.NotNull(taskScheduler); @@ -80,6 +212,19 @@ ValueTask TaskFunc(object? s, CancellationToken token) } } + /// + /// Enqueues a function that returns a for execution. + /// + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts a cancellation token and returns a . + /// A cancellation token that can be used to cancel the operation. + /// A representing the completion of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method wraps the non-generic in a with a + /// result to match the scheduler's signature requirement. + /// public static async Task Enqueue(this ITaskScheduler taskScheduler, Func taskFunc, CancellationToken cancellationToken) { Argument.NotNull(taskScheduler); @@ -93,6 +238,17 @@ async ValueTask TaskFunc(CancellationToken token) } } + /// + /// Enqueues a function that returns a for execution without cancellation support. + /// + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts a cancellation token and returns a . + /// A representing the completion of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method uses for the cancellation token. + /// public static async Task Enqueue(this ITaskScheduler taskScheduler, Func taskFunc) { Argument.NotNull(taskScheduler); @@ -100,6 +256,19 @@ public static async Task Enqueue(this ITaskScheduler taskScheduler, Func + /// Enqueues a function that returns a for execution without cancellation support. + /// + /// The type of result produced by the function. + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts a cancellation token and returns a . + /// A representing the result of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method uses for the cancellation token and wraps the + /// in a . + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func> taskFunc) { Argument.NotNull(taskScheduler); @@ -107,6 +276,19 @@ public static Task Enqueue(this ITaskScheduler taskScheduler, Func new ValueTask(taskFunc(token)), CancellationToken.None); } + /// + /// Enqueues a function that returns a for execution without any parameters. + /// + /// The type of result produced by the function. + /// The task scheduler to enqueue the operation on. + /// The function to execute that returns a . + /// A representing the result of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method uses for the cancellation token and ignores the + /// cancellation token parameter when calling the function. + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func> taskFunc) { Argument.NotNull(taskScheduler); @@ -114,6 +296,18 @@ public static Task Enqueue(this ITaskScheduler taskScheduler, Func return taskScheduler.Enqueue(_ => new ValueTask(taskFunc()), CancellationToken.None); } + /// + /// Enqueues a function that returns a for execution without any parameters. + /// + /// The task scheduler to enqueue the operation on. + /// The function to execute that returns a . + /// A representing the completion of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method uses for the cancellation token and ignores the + /// cancellation token parameter when calling the function. + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func taskFunc) { Argument.NotNull(taskScheduler); @@ -121,6 +315,21 @@ public static Task Enqueue(this ITaskScheduler taskScheduler, Func taskFun return taskScheduler.Enqueue(_ => taskFunc(), CancellationToken.None); } + /// + /// Enqueues a function that returns a for execution. + /// + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts a cancellation token and returns a . + /// A cancellation token that can be used to cancel the operation. + /// Dummy parameter to disambiguate from other overloads. Should always be null. + /// A representing the completion of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method wraps the non-generic in a with a + /// result to match the scheduler's signature requirement. The dummy parameter is used + /// to avoid method signature conflicts with other overloads. + /// public static async Task Enqueue(this ITaskScheduler taskScheduler, Func valueTaskFunc, CancellationToken cancellationToken, DummyParameter? _ = null) { Argument.NotNull(taskScheduler); @@ -134,6 +343,19 @@ async ValueTask TaskFunc(CancellationToken token) } } + /// + /// Enqueues a function that returns a for execution without cancellation support. + /// + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts a cancellation token and returns a . + /// Dummy parameter to disambiguate from other overloads. Should always be null. + /// A representing the completion of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method uses for the cancellation token. The dummy parameter + /// is used to avoid method signature conflicts with other overloads. + /// public static async Task Enqueue(this ITaskScheduler taskScheduler, Func valueTaskFunc, DummyParameter? _ = null) { Argument.NotNull(taskScheduler); @@ -141,6 +363,20 @@ public static async Task Enqueue(this ITaskScheduler taskScheduler, Func + /// Enqueues a function that returns a for execution without cancellation support. + /// + /// The type of result produced by the function. + /// The task scheduler to enqueue the operation on. + /// The function to execute that accepts a cancellation token and returns a . + /// Dummy parameter to disambiguate from other overloads. Should always be null. + /// A representing the result of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method uses for the cancellation token. The dummy parameter + /// is used to avoid method signature conflicts with other overloads. + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func> valueTaskFunc, DummyParameter? _ = null) { Argument.NotNull(taskScheduler); @@ -148,6 +384,21 @@ public static Task Enqueue(this ITaskScheduler taskScheduler, Func + /// Enqueues a function that returns a for execution without any parameters. + /// + /// The type of result produced by the function. + /// The task scheduler to enqueue the operation on. + /// The function to execute that returns a . + /// Dummy parameter to disambiguate from other overloads. Should always be null. + /// A representing the result of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method uses for the cancellation token and ignores the + /// cancellation token parameter when calling the function. The dummy parameter is used to avoid method + /// signature conflicts with other overloads. + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func> valueTaskFunc, DummyParameter? _ = null) { Argument.NotNull(taskScheduler); @@ -155,6 +406,20 @@ public static Task Enqueue(this ITaskScheduler taskScheduler, Func valueTaskFunc(), CancellationToken.None); } + /// + /// Enqueues a function that returns a for execution without any parameters. + /// + /// The task scheduler to enqueue the operation on. + /// The function to execute that returns a . + /// Dummy parameter to disambiguate from other overloads. Should always be null. + /// A representing the completion of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method uses for the cancellation token and ignores the + /// cancellation token parameter when calling the function. The dummy parameter is used to avoid method + /// signature conflicts with other overloads. + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func valueTaskFunc, DummyParameter? _ = null) { Argument.NotNull(taskScheduler); @@ -162,6 +427,19 @@ public static Task Enqueue(this ITaskScheduler taskScheduler, Func va return taskScheduler.Enqueue(_ => valueTaskFunc(), CancellationToken.None, _); } + /// + /// Enqueues an action for execution with cancellation support. + /// + /// The task scheduler to enqueue the operation on. + /// The action to execute that accepts a cancellation token. + /// A cancellation token that can be used to cancel the operation. + /// A representing the completion of the enqueued action. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method wraps the synchronous action in a to match the scheduler's + /// asynchronous interface. + /// public static async Task Enqueue(this ITaskScheduler taskScheduler, Action action, CancellationToken cancellationToken) { Argument.NotNull(taskScheduler); @@ -176,6 +454,18 @@ await taskScheduler.Enqueue( .ConfigureAwait(false); } + /// + /// Enqueues an action for execution without cancellation support. + /// + /// The task scheduler to enqueue the operation on. + /// The action to execute that accepts a cancellation token. + /// A representing the completion of the enqueued action. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method uses for the cancellation token and wraps the + /// synchronous action in a . + /// public static async Task Enqueue(this ITaskScheduler taskScheduler, Action action) { Argument.NotNull(taskScheduler); @@ -190,6 +480,18 @@ await taskScheduler.Enqueue( .ConfigureAwait(false); } + /// + /// Enqueues an action for execution without any parameters. + /// + /// The task scheduler to enqueue the operation on. + /// The action to execute. + /// A representing the completion of the enqueued action. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method uses for the cancellation token, ignores the + /// cancellation token parameter when calling the action, and wraps the synchronous action in a . + /// public static async Task Enqueue(this ITaskScheduler taskScheduler, Action action) { Argument.NotNull(taskScheduler); @@ -204,6 +506,19 @@ await taskScheduler.Enqueue( .ConfigureAwait(false); } + /// + /// Enqueues an action for execution with cancellation support. + /// + /// The task scheduler to enqueue the operation on. + /// The action to execute. + /// A cancellation token that can be used to cancel the operation. + /// A representing the completion of the enqueued action. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method ignores the cancellation token parameter when calling the action and wraps the + /// synchronous action in a . + /// public static async Task Enqueue(this ITaskScheduler taskScheduler, Action action, CancellationToken cancellationToken) { Argument.NotNull(taskScheduler); @@ -218,6 +533,19 @@ await taskScheduler.Enqueue( .ConfigureAwait(false); } + /// + /// Enqueues a synchronous function for execution. + /// + /// The type of result produced by the function. + /// The task scheduler to enqueue the operation on. + /// The synchronous function to execute. + /// A representing the result of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method uses for the cancellation token, ignores the + /// cancellation token parameter when calling the function, and wraps the synchronous result in a . + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func func) { Argument.NotNull(taskScheduler); @@ -225,6 +553,20 @@ public static Task Enqueue(this ITaskScheduler taskScheduler, Func func return taskScheduler.Enqueue(_ => new ValueTask(func()), CancellationToken.None); } + /// + /// Enqueues a synchronous function for execution with cancellation support. + /// + /// The type of result produced by the function. + /// The task scheduler to enqueue the operation on. + /// The synchronous function to execute that accepts a cancellation token. + /// A cancellation token that can be used to cancel the operation. + /// A representing the result of the enqueued function. + /// Thrown when is null. + /// Thrown if the scheduler has been disposed. + /// + /// This method wraps the synchronous result in a to match the scheduler's + /// asynchronous interface. + /// public static Task Enqueue(this ITaskScheduler taskScheduler, Func func, CancellationToken cancellationToken) { Argument.NotNull(taskScheduler); @@ -236,6 +578,11 @@ public static Task Enqueue(this ITaskScheduler taskScheduler, Func. /// + /// + /// This type is used as a dummy parameter in method overloads to disambiguate between methods that would + /// otherwise have identical signatures. It ensures that the parameter can only be null and should + /// never be explicitly set by callers. + /// [EditorBrowsable(EditorBrowsableState.Never)] [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Not intended to use")] public sealed class DummyParameter