1+ #if NET6_0_OR_GREATER
2+ using System ;
3+ using System . Collections . Generic ;
4+ using System . Linq ;
5+ using System . Runtime . CompilerServices ;
6+ using System . Threading ;
7+ using System . Threading . Tasks ;
8+ using EnumerableAsyncProcessor . Extensions ;
9+
10+ namespace EnumerableAsyncProcessor . Example ;
11+
12+ /// <summary>
13+ /// Examples demonstrating proper disposal patterns for EnumerableAsyncProcessor objects.
14+ /// This addresses the common question: "How/when to correctly dispose the resulting processor objects?"
15+ ///
16+ /// QUICK ANSWER: Always dispose processor objects using 'await using' or manual disposal!
17+ ///
18+ /// The key pattern is:
19+ /// ❌ BAD: var processor = items.SelectAsync(...).ProcessInParallel(); // Never disposed!
20+ /// ✅ GOOD: await using var processor = items.SelectAsync(...).ProcessInParallel(); // Auto-disposed!
21+ /// </summary>
22+ public static class DisposalExample
23+ {
24+ public static async Task RunExamples ( )
25+ {
26+ Console . WriteLine ( "Disposal Pattern Examples" ) ;
27+ Console . WriteLine ( "========================\n " ) ;
28+
29+ // Example 1: The problematic pattern from the issue
30+ Console . WriteLine ( "Example 1: PROBLEMATIC - No disposal (resource leak!)" ) ;
31+ var results1 = await ProblematicPatternAsync ( new [ ] { 1 , 2 , 3 , 4 , 5 } , CancellationToken . None ) ;
32+ Console . WriteLine ( $ "Results: { string . Join ( ", " , results1 . ToList ( ) ) } ") ;
33+ Console . WriteLine ( "⚠️ This pattern leaks resources because the processor is never disposed!\n " ) ;
34+
35+ // Example 2: Proper disposal with await using
36+ Console . WriteLine ( "Example 2: PROPER - Using await using for automatic disposal" ) ;
37+ var results2 = await ProperPatternWithAwaitUsingAsync ( new [ ] { 1 , 2 , 3 , 4 , 5 } , CancellationToken . None ) ;
38+ Console . WriteLine ( $ "Results: { string . Join ( ", " , results2 . ToList ( ) ) } ") ;
39+ Console . WriteLine ( "✅ Resources automatically cleaned up with await using\n " ) ;
40+
41+ // Example 3: Proper disposal with manual try-finally
42+ Console . WriteLine ( "Example 3: PROPER - Manual disposal with try-finally" ) ;
43+ var results3 = await ProperPatternWithManualDisposalAsync ( new [ ] { 1 , 2 , 3 , 4 , 5 } , CancellationToken . None ) ;
44+ Console . WriteLine ( $ "Results: { string . Join ( ", " , results3 . ToList ( ) ) } ") ;
45+ Console . WriteLine ( "✅ Resources manually cleaned up in finally block\n " ) ;
46+
47+ // Example 4: Using the convenience extension (no disposal needed)
48+ Console . WriteLine ( "Example 4: CONVENIENT - Using extension methods (disposal handled internally)" ) ;
49+ var asyncEnumerable = GenerateAsyncEnumerable ( 5 ) ;
50+ var results4 = await asyncEnumerable . ProcessInParallel ( async item =>
51+ {
52+ await Task . Delay ( 50 ) ;
53+ return item * 2 ;
54+ } ) ;
55+ Console . WriteLine ( $ "Results: { string . Join ( ", " , results4 ) } ") ;
56+ Console . WriteLine ( "✅ Extension methods handle disposal internally\n " ) ;
57+
58+ // Example 5: Streaming results with proper disposal
59+ Console . WriteLine ( "Example 5: STREAMING - Processing results as they arrive with proper disposal" ) ;
60+ await StreamingWithProperDisposalAsync ( new [ ] { 1 , 2 , 3 , 4 , 5 } , CancellationToken . None ) ;
61+ Console . WriteLine ( "✅ Streamed results with proper disposal\n " ) ;
62+ }
63+
64+ /// <summary>
65+ /// This is the PROBLEMATIC pattern from the GitHub issue - it leaks resources!
66+ /// DO NOT USE THIS PATTERN in production code.
67+ /// </summary>
68+ private static async Task < IAsyncEnumerable < int > > ProblematicPatternAsync ( int [ ] input , CancellationToken token )
69+ {
70+ // ⚠️ PROBLEM: The processor is created but never disposed!
71+ var batchProcessor = input . SelectAsync ( static v => TransformAsync ( v ) , token ) . ProcessInParallel ( ) ;
72+
73+ // This returns the async enumerable, but the processor that created it is never disposed
74+ return batchProcessor . GetResultsAsyncEnumerable ( ) ;
75+
76+ // 🔥 RESOURCE LEAK: The processor goes out of scope without being disposed,
77+ // potentially leaving tasks running and resources uncleaned
78+ }
79+
80+ /// <summary>
81+ /// PROPER pattern using await using for automatic disposal.
82+ /// This is the recommended approach.
83+ /// </summary>
84+ private static async Task < IAsyncEnumerable < int > > ProperPatternWithAwaitUsingAsync ( int [ ] input , CancellationToken token )
85+ {
86+ // ✅ Create processor with await using for automatic disposal
87+ await using var processor = input . SelectAsync ( static v => TransformAsync ( v ) , token ) . ProcessInParallel ( ) ;
88+
89+ // Collect results into a list to return
90+ var results = new List < int > ( ) ;
91+ await foreach ( var result in processor . GetResultsAsyncEnumerable ( ) )
92+ {
93+ results . Add ( result ) ;
94+ }
95+
96+ // Return as async enumerable
97+ return results . ToAsyncEnumerable ( ) ;
98+
99+ // ✅ Processor is automatically disposed here due to 'await using'
100+ }
101+
102+ /// <summary>
103+ /// PROPER pattern using manual disposal with try-finally.
104+ /// Use this when you need more control over the disposal timing.
105+ /// </summary>
106+ private static async Task < IAsyncEnumerable < int > > ProperPatternWithManualDisposalAsync ( int [ ] input , CancellationToken token )
107+ {
108+ var processor = input . SelectAsync ( static v => TransformAsync ( v ) , token ) . ProcessInParallel ( ) ;
109+
110+ try
111+ {
112+ // Collect results into a list to return
113+ var results = new List < int > ( ) ;
114+ await foreach ( var result in processor . GetResultsAsyncEnumerable ( ) )
115+ {
116+ results . Add ( result ) ;
117+ }
118+
119+ return results . ToAsyncEnumerable ( ) ;
120+ }
121+ finally
122+ {
123+ // ✅ Manually dispose the processor to clean up resources
124+ await processor . DisposeAsync ( ) ;
125+ }
126+ }
127+
128+ /// <summary>
129+ /// Example of streaming results while maintaining proper disposal.
130+ /// This shows how to process results as they arrive.
131+ /// </summary>
132+ private static async Task StreamingWithProperDisposalAsync ( int [ ] input , CancellationToken token )
133+ {
134+ await using var processor = input . SelectAsync ( static v => TransformAsync ( v ) , token ) . ProcessInParallel ( ) ;
135+
136+ var processedCount = 0 ;
137+ await foreach ( var result in processor . GetResultsAsyncEnumerable ( ) )
138+ {
139+ processedCount ++ ;
140+ Console . WriteLine ( $ " Received result { processedCount } : { result } ") ;
141+ }
142+
143+ // Processor automatically disposed here
144+ }
145+
146+ /// <summary>
147+ /// Simulates an async transformation operation
148+ /// </summary>
149+ private static async Task < int > TransformAsync ( int value )
150+ {
151+ // Simulate some async work
152+ await Task . Delay ( 50 ) ;
153+ return value * 10 ;
154+ }
155+
156+ /// <summary>
157+ /// Generates an async enumerable for testing
158+ /// </summary>
159+ private static async IAsyncEnumerable < int > GenerateAsyncEnumerable (
160+ int count ,
161+ [ EnumeratorCancellation ] CancellationToken cancellationToken = default )
162+ {
163+ for ( int i = 1 ; i <= count ; i ++ )
164+ {
165+ await Task . Yield ( ) ;
166+ cancellationToken . ThrowIfCancellationRequested ( ) ;
167+ yield return i ;
168+ }
169+ }
170+ }
171+ #endif
0 commit comments