diff --git a/EnumerableAsyncProcessor.Example/DisposalExample.cs b/EnumerableAsyncProcessor.Example/DisposalExample.cs index 14ffe2b..c1769f4 100644 --- a/EnumerableAsyncProcessor.Example/DisposalExample.cs +++ b/EnumerableAsyncProcessor.Example/DisposalExample.cs @@ -1,7 +1,6 @@ #if NET6_0_OR_GREATER using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -29,19 +28,19 @@ public static async Task RunExamples() // Example 1: The problematic pattern from the issue Console.WriteLine("Example 1: PROBLEMATIC - No disposal (resource leak!)"); var results1 = await ProblematicPatternAsync(new[] { 1, 2, 3, 4, 5 }, CancellationToken.None); - Console.WriteLine($"Results: {string.Join(", ", results1.ToList())}"); + Console.WriteLine($"Results: {string.Join(", ", results1)}"); Console.WriteLine("⚠️ This pattern leaks resources because the processor is never disposed!\n"); // Example 2: Proper disposal with await using Console.WriteLine("Example 2: PROPER - Using await using for automatic disposal"); var results2 = await ProperPatternWithAwaitUsingAsync(new[] { 1, 2, 3, 4, 5 }, CancellationToken.None); - Console.WriteLine($"Results: {string.Join(", ", results2.ToList())}"); + Console.WriteLine($"Results: {string.Join(", ", results2)}"); Console.WriteLine("✅ Resources automatically cleaned up with await using\n"); // Example 3: Proper disposal with manual try-finally Console.WriteLine("Example 3: PROPER - Manual disposal with try-finally"); var results3 = await ProperPatternWithManualDisposalAsync(new[] { 1, 2, 3, 4, 5 }, CancellationToken.None); - Console.WriteLine($"Results: {string.Join(", ", results3.ToList())}"); + Console.WriteLine($"Results: {string.Join(", ", results3)}"); Console.WriteLine("✅ Resources manually cleaned up in finally block\n"); // Example 4: Using the convenience extension (no disposal needed) @@ -65,23 +64,27 @@ public static async Task RunExamples() /// This is the PROBLEMATIC pattern from the GitHub issue - it leaks resources! /// DO NOT USE THIS PATTERN in production code. /// - private static async Task> ProblematicPatternAsync(int[] input, CancellationToken token) + private static async Task> ProblematicPatternAsync(int[] input, CancellationToken token) { // ⚠️ PROBLEM: The processor is created but never disposed! var batchProcessor = input.SelectAsync(static v => TransformAsync(v), token).ProcessInParallel(); - - // This returns the async enumerable, but the processor that created it is never disposed - return batchProcessor.GetResultsAsyncEnumerable(); - + + var results = new List(); + await foreach (var result in batchProcessor.GetResultsAsyncEnumerable()) + { + results.Add(result); + } + // 🔥 RESOURCE LEAK: The processor goes out of scope without being disposed, // potentially leaving tasks running and resources uncleaned + return results; } /// /// PROPER pattern using await using for automatic disposal. /// This is the recommended approach. /// - private static async Task> ProperPatternWithAwaitUsingAsync(int[] input, CancellationToken token) + private static async Task> ProperPatternWithAwaitUsingAsync(int[] input, CancellationToken token) { // ✅ Create processor with await using for automatic disposal await using var processor = input.SelectAsync(static v => TransformAsync(v), token).ProcessInParallel(); @@ -93,17 +96,15 @@ private static async Task> ProperPatternWithAwaitUsingAsyn results.Add(result); } - // Return as async enumerable - return results.ToAsyncEnumerable(); - // ✅ Processor is automatically disposed here due to 'await using' + return results; } /// /// PROPER pattern using manual disposal with try-finally. /// Use this when you need more control over the disposal timing. /// - private static async Task> ProperPatternWithManualDisposalAsync(int[] input, CancellationToken token) + private static async Task> ProperPatternWithManualDisposalAsync(int[] input, CancellationToken token) { var processor = input.SelectAsync(static v => TransformAsync(v), token).ProcessInParallel(); @@ -116,7 +117,7 @@ private static async Task> ProperPatternWithManualDisposal results.Add(result); } - return results.ToAsyncEnumerable(); + return results; } finally { @@ -168,4 +169,4 @@ private static async IAsyncEnumerable GenerateAsyncEnumerable( } } } -#endif \ No newline at end of file +#endif diff --git a/EnumerableAsyncProcessor.Pipeline/Modules/BuildExampleProjectsModule.cs b/EnumerableAsyncProcessor.Pipeline/Modules/BuildExampleProjectsModule.cs new file mode 100644 index 0000000..6ea0c59 --- /dev/null +++ b/EnumerableAsyncProcessor.Pipeline/Modules/BuildExampleProjectsModule.cs @@ -0,0 +1,32 @@ +using ModularPipelines.Context; +using ModularPipelines.DotNet.Extensions; +using ModularPipelines.DotNet.Options; +using ModularPipelines.Git.Extensions; +using ModularPipelines.Models; +using ModularPipelines.Modules; + +namespace EnumerableAsyncProcessor.Pipeline.Modules; + +public class BuildExampleProjectsModule : Module> +{ + protected override async Task?> ExecuteAsync( + IModuleContext context, + CancellationToken cancellationToken) + { + var results = new List(); + + foreach (var exampleProjectFile in context + .Git().RootDirectory + .GetFiles(file => file.Path.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase) + && file.Path.Contains("Example", StringComparison.OrdinalIgnoreCase))) + { + results.Add(await context.DotNet().Build(new DotNetBuildOptions + { + ProjectSolution = exampleProjectFile.Path, + Configuration = "Release", + }, cancellationToken: cancellationToken)); + } + + return results; + } +} diff --git a/EnumerableAsyncProcessor.Pipeline/Modules/PackProjectsModule.cs b/EnumerableAsyncProcessor.Pipeline/Modules/PackProjectsModule.cs index a6bf5fe..2777d1b 100644 --- a/EnumerableAsyncProcessor.Pipeline/Modules/PackProjectsModule.cs +++ b/EnumerableAsyncProcessor.Pipeline/Modules/PackProjectsModule.cs @@ -13,6 +13,7 @@ namespace EnumerableAsyncProcessor.Pipeline.Modules; [DependsOn] [DependsOn] [DependsOn] +[DependsOn] public class PackProjectsModule : Module> { protected override async Task?> ExecuteAsync(IModuleContext context, CancellationToken cancellationToken) diff --git a/EnumerableAsyncProcessor.Pipeline/Program.cs b/EnumerableAsyncProcessor.Pipeline/Program.cs index dfe0c87..3b28668 100644 --- a/EnumerableAsyncProcessor.Pipeline/Program.cs +++ b/EnumerableAsyncProcessor.Pipeline/Program.cs @@ -26,7 +26,8 @@ builder.AddModule(); } -builder.AddModule() +builder.AddModule() + .AddModule() .AddModule() .AddModule() .AddModule()