@@ -111,31 +111,60 @@ public ValueTask FullDuplexAsync<T>(
111
111
await consumer ( value , context ) . ConfigureAwait ( false ) ;
112
112
}
113
113
} , context . CancellationToken ) ;
114
- var produced = producer ( context ) ;
114
+ ValueTask produced ;
115
+ try
116
+ {
117
+ produced = producer ( context ) ;
118
+ }
119
+ catch ( Exception ex )
120
+ {
121
+ var knownCancellation = CancelAndDisposeTaskSource ( ref allDone ) ;
122
+ return ObserveErrorAsync ( consumed , ex , knownCancellation ) ;
123
+ }
115
124
if ( produced . IsCompletedSuccessfully ) return new ValueTask ( consumed ) ;
116
125
return BothAsync ( produced , consumed , SwapOut ( ref allDone ) ) ;
117
126
}
118
127
finally
119
128
{
120
129
// stop the producer, in any exit scenario
121
- CancelAndDisposeTaskSource ( allDone ) ;
130
+ CancelAndDisposeTaskSource ( ref allDone ) ;
122
131
}
123
132
}
124
133
125
- static T ? SwapOut < T > ( ref T ? value ) where T : class
134
+ private static T ? SwapOut < T > ( ref T ? value ) where T : class
126
135
{
127
136
var tmp = value ;
128
137
value = default ;
129
138
return tmp ;
130
139
}
131
140
132
- static void CancelAndDisposeTaskSource ( CancellationTokenSource ? cts )
141
+ private static CancellationToken CancelAndDisposeTaskSource ( ref CancellationTokenSource ? cts )
133
142
{
143
+ CancellationToken result = default ;
134
144
if ( cts is object )
135
145
{
136
- cts . Cancel ( ) ;
137
- cts . Dispose ( ) ;
146
+ try { result = cts . Token ; } catch { }
147
+ try { cts . Cancel ( ) ; } catch { }
148
+ try { cts . Dispose ( ) ; } catch { }
149
+ }
150
+ cts = null ;
151
+ return result ;
152
+ }
153
+
154
+ private static async ValueTask ObserveErrorAsync ( Task consumed , Exception producerEx , CancellationToken knownCancellation )
155
+ {
156
+ try
157
+ {
158
+ await consumed . ConfigureAwait ( false ) ; // make sure we try and await both
138
159
}
160
+ catch ( OperationCanceledException oce ) when ( oce . CancellationToken == knownCancellation )
161
+ { } // just throw the real exception
162
+ catch ( Exception consumerEx )
163
+ {
164
+ // so they *both* failed; talk about embarrassing!
165
+ throw new AggregateException ( producerEx , consumerEx ) ;
166
+ }
167
+ throw producerEx ;
139
168
}
140
169
141
170
private static async ValueTask BothAsync ( ValueTask produced , Task consumed , CancellationTokenSource ? allDone )
@@ -148,24 +177,16 @@ private static async ValueTask BothAsync(ValueTask produced, Task consumed, Canc
148
177
}
149
178
catch ( Exception producerEx )
150
179
{
151
- try
152
- {
153
- await consumed . ConfigureAwait ( false ) ; // make sure we try and await both
154
- }
155
- catch ( Exception consumerEx )
156
- {
157
- // so they *both* failed; talk about embarrassing!
158
- throw new AggregateException ( producerEx , consumerEx ) ;
159
- }
160
- throw ; // re-throw the exception from the producer
180
+ var knownCancellation = CancelAndDisposeTaskSource ( ref allDone ) ;
181
+ await ObserveErrorAsync ( consumed , producerEx , knownCancellation ) . ConfigureAwait ( false ) ;
161
182
}
162
183
// producer completed cleanly; we can just await the
163
184
// consumer - if it throws, it throws
164
185
await consumed . ConfigureAwait ( false ) ;
165
186
}
166
187
finally
167
188
{
168
- CancelAndDisposeTaskSource ( allDone ) ;
189
+ CancelAndDisposeTaskSource ( ref allDone ) ;
169
190
}
170
191
}
171
192
@@ -182,14 +203,23 @@ public ValueTask FullDuplexAsync<T>(
182
203
{
183
204
var context = new CallContext ( this , allDone . Token ) ;
184
205
var consumed = Task . Run ( ( ) => consumer ( source , context ) . AsTask ( ) , context . CancellationToken ) ; // note this shares a capture scope
185
- var produced = producer ( context ) ;
206
+ ValueTask produced ;
207
+ try
208
+ {
209
+ produced = producer ( context ) ;
210
+ }
211
+ catch ( Exception ex )
212
+ {
213
+ var knownCancellation = CancelAndDisposeTaskSource ( ref allDone ) ;
214
+ return ObserveErrorAsync ( consumed , ex , knownCancellation ) ;
215
+ }
186
216
if ( produced . IsCompletedSuccessfully ) return new ValueTask ( consumed ) ;
187
217
return BothAsync ( produced , consumed , SwapOut ( ref allDone ) ) ;
188
218
}
189
219
finally
190
220
{
191
221
// stop the producer, in any exit scenario
192
- CancelAndDisposeTaskSource ( allDone ) ;
222
+ CancelAndDisposeTaskSource ( ref allDone ) ;
193
223
}
194
224
}
195
225
0 commit comments