Skip to content

Commit 2421e4b

Browse files
committed
Expose some internal APIs to build custom synchronization primitives
1 parent d7253fa commit 2421e4b

File tree

3 files changed

+89
-119
lines changed

3 files changed

+89
-119
lines changed

src/DotNext.Threading/Threading/Tasks/ManualResetCompletionSource.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ protected object? CompletionData
150150
/// <summary>
151151
/// Invokes continuation callback and cleanup state of this source.
152152
/// </summary>
153-
internal void Resume()
153+
protected internal void Resume()
154154
{
155155
state.Detach().Dispose();
156156

@@ -261,15 +261,17 @@ public void OnCompleted(Action<object?> continuation, object? state, short token
261261
/// <param name="token">The canceled token.</param>
262262
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
263263
[MethodImpl(MethodImplOptions.AggressiveInlining)]
264-
public bool TrySetCanceled(CancellationToken token) => TrySetCanceled(null, token);
264+
public bool TrySetCanceled(CancellationToken token)
265+
=> TrySetException(new OperationCanceledException(token));
265266

266267
/// <summary>
267268
/// Attempts to complete the task unsuccessfully.
268269
/// </summary>
269270
/// <param name="completionData">The data to be saved in <see cref="CompletionData"/> property that can be accessed from within <see cref="AfterConsumed"/> method.</param>
270271
/// <param name="token">The canceled token.</param>
271272
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
272-
public abstract bool TrySetCanceled(object? completionData, CancellationToken token);
273+
public bool TrySetCanceled(object? completionData, CancellationToken token)
274+
=> TrySetException(completionData, new OperationCanceledException(token));
273275

274276
/// <summary>
275277
/// Gets the status of this source.

src/DotNext.Threading/Threading/Tasks/ValueTaskCompletionSource.T.cs

Lines changed: 33 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ public ValueTaskCompletionSource(bool runContinuationsAsynchronously = true)
3434
{
3535
}
3636

37-
private static Result<T> FromCanceled(CancellationToken token)
38-
=> new(new OperationCanceledException(token));
39-
4037
/// <summary>
4138
/// Attempts to complete the task successfully.
4239
/// </summary>
@@ -52,18 +49,8 @@ public bool TrySetResult(T value)
5249
/// <param name="completionData">The data to be saved in <see cref="ManualResetCompletionSource.CompletionData"/> property that can be accessed from within <see cref="ManualResetCompletionSource.AfterConsumed"/> method.</param>
5350
/// <param name="value">The value to be returned to the consumer.</param>
5451
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
55-
public unsafe bool TrySetResult(object? completionData, T value)
56-
=> SetResult(completionData, completionToken: null, &Result.FromValue, value);
57-
58-
/// <summary>
59-
/// Attempts to complete the task successfully.
60-
/// </summary>
61-
/// <param name="completionToken">The completion token previously obtained from <see cref="CreateTask(TimeSpan, CancellationToken)"/> method.</param>
62-
/// <param name="value">The value to be returned to the consumer.</param>
63-
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
64-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
65-
public bool TrySetResult(short completionToken, T value)
66-
=> TrySetResult(null, completionToken, value);
52+
public bool TrySetResult(object? completionData, T value)
53+
=> TrySetResult(completionData, completionToken: null, new Result<T>(value));
6754

6855
/// <summary>
6956
/// Attempts to complete the task successfully.
@@ -72,22 +59,12 @@ public bool TrySetResult(short completionToken, T value)
7259
/// <param name="completionToken">The completion token previously obtained from <see cref="CreateTask(TimeSpan, CancellationToken)"/> method.</param>
7360
/// <param name="value">The value to be returned to the consumer.</param>
7461
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
75-
public unsafe bool TrySetResult(object? completionData, short completionToken, T value)
76-
=> SetResult(completionData, completionToken, &Result.FromValue, value);
62+
public bool TrySetResult(object? completionData, short completionToken, T value)
63+
=> TrySetResult(completionData, completionToken, new Result<T>(value));
7764

7865
/// <inheritdoc />
79-
public sealed override unsafe bool TrySetException(object? completionData, Exception e)
80-
=> SetResult(completionData, completionToken: null, &Result.FromException<T>, e);
81-
82-
/// <summary>
83-
/// Attempts to complete the task unsuccessfully.
84-
/// </summary>
85-
/// <param name="completionToken">The completion token previously obtained from <see cref="CreateTask(TimeSpan, CancellationToken)"/> method.</param>
86-
/// <param name="e">The exception to be returned to the consumer.</param>
87-
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
88-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
89-
public bool TrySetException(short completionToken, Exception e)
90-
=> TrySetException(null, completionToken, e);
66+
public sealed override bool TrySetException(object? completionData, Exception e)
67+
=> TrySetResult(completionData, completionToken: null, new Result<T>(e));
9168

9269
/// <summary>
9370
/// Attempts to complete the task unsuccessfully.
@@ -96,22 +73,8 @@ public bool TrySetException(short completionToken, Exception e)
9673
/// <param name="completionToken">The completion token previously obtained from <see cref="CreateTask(TimeSpan, CancellationToken)"/> method.</param>
9774
/// <param name="e">The exception to be returned to the consumer.</param>
9875
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
99-
public unsafe bool TrySetException(object? completionData, short completionToken, Exception e)
100-
=> SetResult(completionData, completionToken, &Result.FromException<T>, e);
101-
102-
/// <inheritdoc />
103-
public sealed override unsafe bool TrySetCanceled(object? completionData, CancellationToken token)
104-
=> SetResult(completionData, completionToken: null, &FromCanceled, token);
105-
106-
/// <summary>
107-
/// Attempts to complete the task unsuccessfully.
108-
/// </summary>
109-
/// <param name="completionToken">The completion token previously obtained from <see cref="CreateTask(TimeSpan, CancellationToken)"/> method.</param>
110-
/// <param name="token">The canceled token.</param>
111-
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
112-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
113-
public bool TrySetCanceled(short completionToken, CancellationToken token)
114-
=> TrySetCanceled(null, completionToken, token);
76+
public bool TrySetException(object? completionData, short completionToken, Exception e)
77+
=> TrySetResult(completionData, completionToken, new Result<T>(e));
11578

11679
/// <summary>
11780
/// Attempts to complete the task unsuccessfully.
@@ -120,31 +83,24 @@ public bool TrySetCanceled(short completionToken, CancellationToken token)
12083
/// <param name="completionToken">The completion token previously obtained from <see cref="CreateTask(TimeSpan, CancellationToken)"/> method.</param>
12184
/// <param name="token">The canceled token.</param>
12285
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
123-
public unsafe bool TrySetCanceled(object? completionData, short completionToken, CancellationToken token)
124-
=> SetResult(completionData, completionToken, &FromCanceled, token);
86+
public bool TrySetCanceled(object? completionData, short completionToken, CancellationToken token)
87+
=> TrySetException(completionData, completionToken, new OperationCanceledException(token));
12588

12689
private protected sealed override bool CompleteAsTimedOut()
12790
=> SetResult(OnTimeout());
12891

12992
private protected sealed override bool CompleteAsCanceled(CancellationToken token)
13093
=> SetResult(OnCanceled(token));
13194

132-
private unsafe bool SetResult<TArg>(object? completionData, short? completionToken, delegate*<TArg, Result<T>> func, TArg arg)
95+
private bool TrySetResult(object? completionData, short? completionToken, in Result<T> result)
13396
{
134-
Debug.Assert(func != null);
135-
136-
bool result;
137-
lock (SyncRoot)
97+
var completed = TrySetResult(completionData, completionToken, in result, out var resumable);
98+
if (resumable)
13899
{
139-
result = versionAndStatus.CanBeCompleted(completionToken);
140-
if (!result || !SetResult(func(arg), completionData))
141-
goto exit;
100+
Resume();
142101
}
143102

144-
Resume();
145-
146-
exit:
147-
return result;
103+
return completed;
148104
}
149105

150106
private bool SetResult(in Result<T> result, object? completionData = null)
@@ -155,15 +111,29 @@ private bool SetResult(in Result<T> result, object? completionData = null)
155111
return SetResult(completionData);
156112
}
157113

158-
internal bool TrySetResult(object? completionData, short? completionToken, in Result<T> result, out bool resumable)
114+
/// <summary>
115+
/// Tries to set the result of this source without resuming the <see cref="ValueTask{TResult}"/> consumer.
116+
/// </summary>
117+
/// <param name="completionData">The completion data to be assigned to <see cref="ManualResetCompletionSource.CompletionData"/> property.</param>
118+
/// <param name="completionToken">The optional completion token.</param>
119+
/// <param name="result">The result to be stored as the result of <see cref="ValueTask{TResult}"/>.</param>
120+
/// <param name="resumable">
121+
/// <see langword="true"/> if <see cref="ManualResetCompletionSource.Resume()"/> needs to be called to resume
122+
/// the consumer of <see cref="ValueTask{TResult}"/>.
123+
/// </param>
124+
/// <returns>
125+
/// <see langword="true"/> if this source is completed successfully;
126+
/// <see langword="false"/> if this source was completed previously.
127+
/// </returns>
128+
protected internal bool TrySetResult(object? completionData, short? completionToken, in Result<T> result, out bool resumable)
159129
{
160-
bool successful;
130+
bool completed;
161131
lock (SyncRoot)
162132
{
163-
resumable = (successful = versionAndStatus.CanBeCompleted(completionToken)) && SetResult(in result, completionData);
133+
resumable = (completed = versionAndStatus.CanBeCompleted(completionToken)) && SetResult(in result, completionData);
164134
}
165135

166-
return successful;
136+
return completed;
167137
}
168138

169139
/// <inheritdoc />

src/DotNext.Threading/Threading/Tasks/ValueTaskCompletionSource.cs

Lines changed: 51 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,23 @@ public ValueTaskCompletionSource(bool runContinuationsAsynchronously = true)
2828
{
2929
}
3030

31-
private bool SetResult(Exception? result, object? completionData = null)
31+
private protected sealed override bool CompleteAsTimedOut()
3232
{
33-
AssertLocked();
33+
var dispatchInfo = OnTimeout() is { } e
34+
? ExceptionDispatchInfo.Capture(e)
35+
: null;
3436

35-
this.result = result is null ? null : ExceptionDispatchInfo.Capture(result);
36-
return SetResult(completionData);
37+
return SetResult(dispatchInfo);
3738
}
3839

39-
private protected sealed override bool CompleteAsTimedOut()
40-
=> SetResult(OnTimeout());
41-
4240
private protected sealed override bool CompleteAsCanceled(CancellationToken token)
43-
=> SetResult(OnCanceled(token));
41+
{
42+
var dispatchInfo = OnCanceled(token) is { } e
43+
? ExceptionDispatchInfo.Capture(e)
44+
: null;
45+
46+
return SetResult(dispatchInfo);
47+
}
4448

4549
/// <inheritdoc />
4650
protected override void CleanUp() => result = null;
@@ -64,37 +68,49 @@ private protected sealed override bool CompleteAsCanceled(CancellationToken toke
6468
/// <returns>The exception representing task result; or <see langword="null"/> to complete successfully.</returns>
6569
protected virtual Exception? OnCanceled(CancellationToken token) => new OperationCanceledException(token);
6670

67-
private bool SetResult<TFactory>(object? completionData, short? completionToken, TFactory factory)
68-
where TFactory : ISupplier<Exception?>
71+
/// <summary>
72+
/// Tries to set the result of this source without resuming the <see cref="ValueTask"/> consumer.
73+
/// </summary>
74+
/// <param name="completionData">The completion data to be assigned to <see cref="ManualResetCompletionSource.CompletionData"/> property.</param>
75+
/// <param name="completionToken">The optional completion token.</param>
76+
/// <param name="dispatchInfo">The exception to be stored as the result of <see cref="ValueTask"/>.</param>
77+
/// <param name="resumable">
78+
/// <see langword="true"/> if <see cref="ManualResetCompletionSource.Resume()"/> needs to be called to resume
79+
/// the consumer of <see cref="ValueTask"/>.
80+
/// </param>
81+
/// <returns>
82+
/// <see langword="true"/> if this source is completed successfully;
83+
/// <see langword="false"/> if this source was completed previously.
84+
/// </returns>
85+
protected internal bool TrySetResult(object? completionData, short? completionToken, ExceptionDispatchInfo? dispatchInfo, out bool resumable)
6986
{
70-
bool result;
87+
bool completed;
7188
lock (SyncRoot)
7289
{
73-
result = versionAndStatus.CanBeCompleted(completionToken);
74-
75-
if (!result || !SetResult(factory.Invoke(), completionData))
76-
goto exit;
90+
resumable = (completed = versionAndStatus.CanBeCompleted(completionToken)) && SetResult(dispatchInfo, completionData);
7791
}
7892

79-
Resume();
93+
return completed;
94+
}
95+
96+
private bool TrySetResult(object? completionData, short? completionToken, ExceptionDispatchInfo? dispatchInfo)
97+
{
98+
var completed = TrySetResult(completionData, completionToken, dispatchInfo, out var resumable);
99+
if (resumable)
100+
{
101+
Resume();
102+
}
80103

81-
exit:
82-
return result;
104+
return completed;
83105
}
84106

85-
/// <inheritdoc />
86-
public sealed override bool TrySetCanceled(object? completionData, CancellationToken token)
87-
=> SetResult<OperationCanceledExceptionFactory>(completionData, completionToken: null, token);
107+
private bool SetResult(ExceptionDispatchInfo? dispatchInfo, object? completionData = null)
108+
{
109+
AssertLocked();
88110

89-
/// <summary>
90-
/// Attempts to complete the task unsuccessfully.
91-
/// </summary>
92-
/// <param name="completionToken">The completion token previously obtained from <see cref="CreateTask(TimeSpan, CancellationToken)"/> method.</param>
93-
/// <param name="token">The canceled token.</param>
94-
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
95-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
96-
public bool TrySetCanceled(short completionToken, CancellationToken token)
97-
=> TrySetCanceled(null, completionToken, token);
111+
result = dispatchInfo;
112+
return SetResult(completionData);
113+
}
98114

99115
/// <summary>
100116
/// Attempts to complete the task unsuccessfully.
@@ -104,21 +120,11 @@ public bool TrySetCanceled(short completionToken, CancellationToken token)
104120
/// <param name="token">The canceled token.</param>
105121
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
106122
public bool TrySetCanceled(object? completionData, short completionToken, CancellationToken token)
107-
=> SetResult<OperationCanceledExceptionFactory>(completionData, completionToken, token);
123+
=> TrySetException(completionData, completionToken, new OperationCanceledException(token));
108124

109125
/// <inheritdoc />
110126
public sealed override bool TrySetException(object? completionData, Exception e)
111-
=> SetResult<ValueSupplier<Exception>>(completionData, completionToken: null, e);
112-
113-
/// <summary>
114-
/// Attempts to complete the task unsuccessfully.
115-
/// </summary>
116-
/// <param name="completionToken">The completion token previously obtained from <see cref="CreateTask(TimeSpan, CancellationToken)"/> method.</param>
117-
/// <param name="e">The exception to be returned to the consumer.</param>
118-
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
119-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
120-
public bool TrySetException(short completionToken, Exception e)
121-
=> TrySetException(null, completionToken, e);
127+
=> TrySetResult(completionData, completionToken: null, ExceptionDispatchInfo.Capture(e));
122128

123129
/// <summary>
124130
/// Attempts to complete the task unsuccessfully.
@@ -128,7 +134,7 @@ public bool TrySetException(short completionToken, Exception e)
128134
/// <param name="e">The exception to be returned to the consumer.</param>
129135
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
130136
public bool TrySetException(object? completionData, short completionToken, Exception e)
131-
=> SetResult<ValueSupplier<Exception>>(completionData, completionToken, e);
137+
=> TrySetResult(completionData, completionToken, ExceptionDispatchInfo.Capture(e));
132138

133139
/// <summary>
134140
/// Attempts to complete the task successfully.
@@ -144,15 +150,7 @@ public bool TrySetResult()
144150
/// <param name="completionData">The data to be saved in <see cref="ManualResetCompletionSource.CompletionData"/> property that can be accessed from within <see cref="ManualResetCompletionSource.AfterConsumed"/> method.</param>
145151
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
146152
public bool TrySetResult(object? completionData)
147-
=> SetResult(completionData, completionToken: null, ISupplier<Exception>.NullOrDefault);
148-
149-
/// <summary>
150-
/// Attempts to complete the task successfully.
151-
/// </summary>
152-
/// <param name="completionToken">The completion token previously obtained from <see cref="CreateTask(TimeSpan, CancellationToken)"/> method.</param>
153-
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
154-
public bool TrySetResult(short completionToken)
155-
=> TrySetResult(null, completionToken);
153+
=> TrySetResult(completionData, completionToken: null, dispatchInfo: null);
156154

157155
/// <summary>
158156
/// Attempts to complete the task successfully.
@@ -161,7 +159,7 @@ public bool TrySetResult(short completionToken)
161159
/// <param name="completionToken">The completion token previously obtained from <see cref="CreateTask(TimeSpan, CancellationToken)"/> method.</param>
162160
/// <returns><see langword="true"/> if the result is completed successfully; <see langword="false"/> if the task has been canceled or timed out.</returns>
163161
public bool TrySetResult(object? completionData, short completionToken)
164-
=> SetResult(completionData, completionToken, ISupplier<Exception>.NullOrDefault);
162+
=> TrySetResult(completionData, completionToken, dispatchInfo: null);
165163

166164
/// <summary>
167165
/// Creates a fresh task linked with this source.

0 commit comments

Comments
 (0)