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