@@ -25,13 +25,15 @@ private enum State : byte
25
25
private State _state ;
26
26
private ManualResetValueTaskSourceCore < bool > _valueTaskSource ;
27
27
private CancellationTokenRegistration _cancellationRegistration ;
28
+ private Exception ? _exception ;
28
29
private GCHandle _keepAlive ;
29
30
30
31
public ValueTaskSource ( )
31
32
{
32
33
_state = State . None ;
33
34
_valueTaskSource = new ManualResetValueTaskSourceCore < bool > ( ) { RunContinuationsAsynchronously = true } ;
34
35
_cancellationRegistration = default ;
36
+ _exception = default ;
35
37
_keepAlive = default ;
36
38
}
37
39
@@ -75,7 +77,7 @@ public bool TryInitialize(out ValueTask valueTask, object? keepAlive = null, Can
75
77
State state = _state ;
76
78
77
79
// If we're the first here, we will return true.
78
- if ( state == State . None )
80
+ if ( state == State . None && _valueTaskSource . GetStatus ( _valueTaskSource . Version ) == ValueTaskSourceStatus . Pending )
79
81
{
80
82
// Keep alive the caller object until the result is read from the task.
81
83
// Used for keeping caller alive during async interop calls.
@@ -104,30 +106,42 @@ private bool TryComplete(Exception? exception)
104
106
{
105
107
State state = _state ;
106
108
107
- if ( state != State . Completed )
109
+ // Completed: nothing to do.
110
+ if ( state == State . Completed )
108
111
{
109
- _state = State . Completed ;
112
+ return false ;
113
+ }
114
+
115
+ // With cancellation, keep the state as-is so it can be restored after the OCE is consumed.
116
+ _state = exception is OperationCanceledException ? state : State . Completed ;
110
117
111
- // Swap the cancellation registration so the one that's been registered gets eventually Disposed.
112
- // Ideally, we would dispose it here, but if the callbacks kicks in, it tries to take the lock held by this thread leading to deadlock.
113
- cancellationRegistration = _cancellationRegistration ;
114
- _cancellationRegistration = default ;
118
+ // Swap the cancellation registration so the one that's been registered gets eventually Disposed.
119
+ // Ideally, we would dispose it here, but if the callbacks kicks in, it tries to take the lock held by this thread leading to deadlock.
120
+ cancellationRegistration = _cancellationRegistration ;
121
+ _cancellationRegistration = default ;
115
122
116
- if ( exception is not null )
123
+ if ( exception is not null )
124
+ {
125
+ // Set up the exception stack trace for the caller.
126
+ exception = exception . StackTrace is null ? ExceptionDispatchInfo . SetCurrentStackTrace ( exception ) : exception ;
127
+ if ( _valueTaskSource . GetStatus ( _valueTaskSource . Version ) == ValueTaskSourceStatus . Pending )
117
128
{
118
- // Set up the exception stack trace for the caller.
119
- exception = exception . StackTrace is null ? ExceptionDispatchInfo . SetCurrentStackTrace ( exception ) : exception ;
120
129
_valueTaskSource . SetException ( exception ) ;
121
130
}
122
- else
131
+ else if ( exception is not OperationCanceledException )
132
+ {
133
+ _exception = exception ;
134
+ }
135
+ }
136
+ else
137
+ {
138
+ if ( _valueTaskSource . GetStatus ( _valueTaskSource . Version ) == ValueTaskSourceStatus . Pending )
123
139
{
124
140
_valueTaskSource . SetResult ( true ) ;
125
141
}
126
-
127
- return true ;
128
142
}
129
143
130
- return false ;
144
+ return true ;
131
145
}
132
146
finally
133
147
{
@@ -173,5 +187,34 @@ void IValueTaskSource.OnCompleted(Action<object?> continuation, object? state, s
173
187
=> _valueTaskSource . OnCompleted ( continuation , state , token , flags ) ;
174
188
175
189
void IValueTaskSource . GetResult ( short token )
176
- => _valueTaskSource . GetResult ( token ) ;
190
+ {
191
+ try
192
+ {
193
+ _valueTaskSource . GetResult ( token ) ;
194
+ }
195
+ finally
196
+ {
197
+ lock ( this )
198
+ {
199
+ State state = _state ;
200
+
201
+ // In case of a cancellation, reset the task and set the stored results if necessary.
202
+ if ( _valueTaskSource . GetStatus ( _valueTaskSource . Version ) == ValueTaskSourceStatus . Canceled )
203
+ {
204
+ _valueTaskSource . Reset ( ) ;
205
+ if ( state == State . Completed )
206
+ {
207
+ if ( _exception is not null )
208
+ {
209
+ _valueTaskSource . SetException ( _exception ) ;
210
+ }
211
+ else
212
+ {
213
+ _valueTaskSource . SetResult ( true ) ;
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
177
220
}
0 commit comments