@@ -26,7 +26,7 @@ internal ResultAsyncEnumerableParallelProcessor(
2626 public async IAsyncEnumerable < TOutput > ExecuteAsync ( )
2727 {
2828 var cancellationToken = _cancellationTokenSource . Token ;
29- var semaphore = new SemaphoreSlim ( _maxConcurrency , _maxConcurrency ) ;
29+ using var semaphore = new SemaphoreSlim ( _maxConcurrency , _maxConcurrency ) ;
3030 var tasks = new List < Task < TOutput > > ( ) ;
3131
3232 try
@@ -36,7 +36,18 @@ public async IAsyncEnumerable<TOutput> ExecuteAsync()
3636 await semaphore . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
3737
3838 var capturedItem = item ;
39- var task = ProcessItemAsync ( capturedItem , semaphore , cancellationToken ) ;
39+ // Use Task.Run to ensure parallelism and prevent blocking
40+ var task = Task . Run ( async ( ) =>
41+ {
42+ try
43+ {
44+ return await _taskSelector ( capturedItem ) . ConfigureAwait ( false ) ;
45+ }
46+ finally
47+ {
48+ semaphore . Release ( ) ;
49+ }
50+ } , cancellationToken ) ;
4051 tasks . Add ( task ) ;
4152
4253 // Yield completed results
@@ -56,24 +67,19 @@ public async IAsyncEnumerable<TOutput> ExecuteAsync()
5667 }
5768 finally
5869 {
59- semaphore . Dispose ( ) ;
60- }
61- }
62-
63- private async Task < TOutput > ProcessItemAsync (
64- TInput item ,
65- SemaphoreSlim semaphore ,
66- CancellationToken cancellationToken )
67- {
68- try
69- {
70- // Yield to ensure we don't block the thread if _taskSelector is synchronous
71- await Task . Yield ( ) ;
72- return await _taskSelector ( item ) . ConfigureAwait ( false ) ;
73- }
74- finally
75- {
76- semaphore . Release ( ) ;
70+ // Ensure all tasks complete before the using block disposes the semaphore
71+ // This handles cancellation or exception scenarios
72+ if ( tasks . Count > 0 )
73+ {
74+ try
75+ {
76+ await Task . WhenAll ( tasks ) . ConfigureAwait ( false ) ;
77+ }
78+ catch
79+ {
80+ // Ignore exceptions here as they've already been handled
81+ }
82+ }
7783 }
7884 }
7985}
0 commit comments