Skip to content

Commit

Permalink
Add more docs and tests (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
sbeca authored Nov 8, 2023
1 parent e31cc3b commit fd4b85f
Show file tree
Hide file tree
Showing 6 changed files with 492 additions and 56 deletions.
2 changes: 1 addition & 1 deletion api_list.include.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@
### Task<TResult>

* `Task<TResult> WaitAsync<TResult>(CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitasync#system-threading-tasks-task-waitasync(system-threading-cancellationtoken))
* `Task<TResult> WaitAsync<TResult>(TimeSpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.waitasync#system-threading-tasks-task-1-waitasync(system-threading-cancellationtoken))
* `Task<TResult> WaitAsync<TResult>(TimeSpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.waitasync#system-threading-tasks-task-1-waitasync(system-timespan))
* `Task<TResult> WaitAsync<TResult>(TimeSpan, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.waitasync#system-threading-tasks-task-1-waitasync(system-timespan-system-threading-cancellationtoken))


Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ The class `PolyfillExtensions` includes the following extension methods:
### Task<TResult>

* `Task<TResult> WaitAsync<TResult>(CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitasync#system-threading-tasks-task-waitasync(system-threading-cancellationtoken))
* `Task<TResult> WaitAsync<TResult>(TimeSpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.waitasync#system-threading-tasks-task-1-waitasync(system-threading-cancellationtoken))
* `Task<TResult> WaitAsync<TResult>(TimeSpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.waitasync#system-threading-tasks-task-1-waitasync(system-timespan))
* `Task<TResult> WaitAsync<TResult>(TimeSpan, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.waitasync#system-threading-tasks-task-1-waitasync(system-timespan-system-threading-cancellationtoken))


Expand Down
45 changes: 42 additions & 3 deletions src/Polyfill/PolyfillExtensions_Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ static partial class PolyfillExtensions
/// Indicates whether a specified value is found in a read-only span. Values are compared using IEquatable{T}.Equals(T).
/// </summary>
/// <param name="value">The value to search for.</param>
/// <returns>true if found, false otherwise.</returns>
/// <returns><c>true</c> if found, <c>false</c> otherwise.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.contains#system-memoryextensions-contains-1(system-readonlyspan((-0))-0)")]
public static bool Contains<T>(
this ReadOnlySpan<T> target,
Expand All @@ -38,7 +38,7 @@ public static bool Contains<T>(
/// Indicates whether a specified value is found in a only span. Values are compared using IEquatable{T}.Equals(T).
/// </summary>
/// <param name="value">The value to search for.</param>
/// <returns>true if found, false otherwise.</returns>
/// <returns><c>true</c> if found, <c>false</c> otherwise.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.contains#system-memoryextensions-contains-1(system-span((-0))-0)")]
public static bool Contains<T>(
this Span<T> target,
Expand All @@ -56,42 +56,81 @@ public static bool Contains<T>(
return false;
}

/// <summary>
/// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T).
/// </summary>
/// <param name="target">The first sequence to compare.</param>
/// <param name="other">The second sequence to compare.</param>
/// <returns><c>true</c> if the two sequences are equal; otherwise, <c>false</c>.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.sequenceequal#system-memoryextensions-sequenceequal-1(system-readonlyspan((-0))-system-readonlyspan((-0)))")]
public static bool SequenceEqual(
this ReadOnlySpan<char> target,
string other) =>
target.SequenceEqual(other.AsSpan());

/// <summary>
/// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T).
/// </summary>
/// <param name="target">The first sequence to compare.</param>
/// <param name="other">The second sequence to compare.</param>
/// <returns><c>true</c> if the two sequences are equal; otherwise, <c>false</c>.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.sequenceequal#system-memoryextensions-sequenceequal-1(system-span((-0))-system-readonlyspan((-0)))")]
public static bool SequenceEqual(
this Span<char> target,
string other) =>
target.SequenceEqual(other.AsSpan());

/// <summary>
/// Determines whether a read-only character span begins with a specified value when compared using a specified <see cref="StringComparison"/> value.
/// </summary>
/// <param name="target">The source span.</param>
/// <param name="other">The sequence to compare to the beginning of the source span.</param>
/// <param name="comparison">An enumeration value that determines how span and value are compared.</param>
/// <returns><c>true</c> if value matches the beginning of span; otherwise, <c>false</c>.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.startswith#system-memoryextensions-startswith-1(system-readonlyspan((-0))-system-readonlyspan((-0)))")]
public static bool StartsWith(
this ReadOnlySpan<char> target,
string other,
StringComparison comparison = StringComparison.CurrentCulture) =>
target.StartsWith(other.AsSpan(), comparison);

/// <summary>
/// Determines whether a specified sequence appears at the start of a span.
/// </summary>
/// <param name="target">The source span.</param>
/// <param name="other">The sequence to compare to the beginning of the source span.</param>
/// <returns><c>true</c> if value matches the beginning of span; otherwise, <c>false</c>.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.startswith#system-memoryextensions-startswith-1(system-span((-0))-system-readonlyspan((-0)))")]
public static bool StartsWith(
this Span<char> target,
string other) =>
target.StartsWith(other.AsSpan());

/// <summary>
/// Determines whether the end of the span matches the specified value when compared using the specified <paramref name="comparison"/> option.
/// </summary>
/// <param name="target">The source span.</param>
/// <param name="other">The sequence to compare to the end of the source span.</param>
/// <param name="comparison">An enumeration value that determines how span and value are compared.</param>
/// <returns><c>true</c> if value matches the end of span; otherwise, <c>false</c>.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.endswith#system-memoryextensions-endswith-1(system-readonlyspan((-0))-system-readonlyspan((-0)))")]
public static bool EndsWith(
this ReadOnlySpan<char> target,
string other,
StringComparison comparison = StringComparison.CurrentCulture) =>
target.EndsWith(other.AsSpan(), comparison);

/// <summary>
/// Determines whether the specified sequence appears at the end of a span.
/// </summary>
/// <param name="target">The source span.</param>
/// <param name="other">The sequence to compare to the end of the source span.</param>
/// <returns><c>true</c> if value matches the end of span; otherwise, <c>false</c>.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.endswith#system-memoryextensions-endswith-1(system-span((-0))-system-readonlyspan((-0)))")]
public static bool EndsWith(
this Span<char> target,
string other) =>
target.EndsWith(other.AsSpan());
}
#endif

#endif
124 changes: 80 additions & 44 deletions src/Polyfill/PolyfillExtensions_Task.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,81 +12,117 @@

static partial class PolyfillExtensions
{
// Copied from .NET library const Timer.MaxSupportedTimeout
private const uint MaxSupportedTimeout = 0xfffffffe;

/// <summary>Gets a <see cref="Task"/> that will complete when this <see cref="Task"/> completes or when the specified <see cref="CancellationToken"/> has cancellation requested.</summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for a cancellation request.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous wait. It may or may not be the same instance as the current instance.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitasync#system-threading-tasks-task-waitasync(system-threading-cancellationtoken)")]
public static Task WaitAsync(this Task target, CancellationToken cancellationToken) =>
target.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken);

/// <summary>Gets a <see cref="Task"/> that will complete when this <see cref="Task"/> completes or when the specified timeout expires.</summary>
/// <param name="timeout">The timeout after which the <see cref="Task"/> should be faulted with a <see cref="TimeoutException"/> if it hasn't otherwise completed.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous wait. It may or may not be the same instance as the current instance.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitasync#system-threading-tasks-task-waitasync(system-timespan)")]
public static async Task WaitAsync(
public static Task WaitAsync(
this Task target,
TimeSpan timeout)
{
var cancelSource = new CancellationTokenSource();
try
{
await target.WaitAsync(timeout, cancelSource.Token);
}
finally
{
cancelSource.Cancel();
cancelSource.Dispose();
}
}
TimeSpan timeout) =>
target.WaitAsync(timeout, default);

/// <summary>Gets a <see cref="Task"/> that will complete when this <see cref="Task"/> completes, when the specified timeout expires, or when the specified <see cref="CancellationToken"/> has cancellation requested.</summary>
/// <param name="timeout">The timeout after which the <see cref="Task"/> should be faulted with a <see cref="TimeoutException"/> if it hasn't otherwise completed.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for a cancellation request.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous wait. It may or may not be the same instance as the current instance.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitasync#system-threading-tasks-task-waitasync(system-timespan-system-threading-cancellationtoken)")]
public static async Task WaitAsync(
public static Task WaitAsync(
this Task target,
TimeSpan timeout,
CancellationToken cancellationToken)
{
var delayTask = Task.Delay(timeout, cancellationToken);
var completedTask = await Task.WhenAny(target, delayTask);
if (completedTask == delayTask)
if (target is null)
{
throw new TimeoutException($"Execution did not complete within the time allotted {timeout.TotalMilliseconds} ms");
throw new ArgumentNullException(nameof(target));
}

await target;
long totalMilliseconds = (long)timeout.TotalMilliseconds;
if (totalMilliseconds < -1 || totalMilliseconds > MaxSupportedTimeout)
{
throw new ArgumentOutOfRangeException(nameof(timeout));
}

if (target.IsCompleted || (!cancellationToken.CanBeCanceled && timeout == Timeout.InfiniteTimeSpan))
{
return target;
}

if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}

if (timeout == TimeSpan.Zero)
{
return Task.FromException(new TimeoutException());
}

var delayTask = Task.Delay(timeout, cancellationToken);

Func<Task<Task>, Task> continueFunc = completedTask =>
{
if (cancellationToken.IsCancellationRequested)
{
throw new TaskCanceledException();
}
else if (completedTask.Result == delayTask)
{
throw new TimeoutException($"Execution did not complete within the time allotted {timeout.TotalMilliseconds} ms");
}

return target;
};

return Task.WhenAny(target, delayTask).ContinueWith(continueFunc, TaskContinuationOptions.OnlyOnRanToCompletion);
}

/// <summary>
/// Gets a <see cref="Task"/> that will complete when this <see cref="Task"/> completes, or when the specified <see cref="CancellationToken"/> has cancellation requested.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for a cancellation request.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous wait. It may or may not be the same instance as the current instance.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitasync#system-threading-tasks-task-waitasync(system-threading-cancellationtoken)")]
public static Task<TResult> WaitAsync<TResult>(
this Task<TResult> target,
CancellationToken cancellationToken) =>
target.WaitAsync<TResult>(Timeout.InfiniteTimeSpan, cancellationToken);

[Link("https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.waitasync#system-threading-tasks-task-1-waitasync(system-threading-cancellationtoken)")]
public static async Task<TResult> WaitAsync<TResult>(
/// <summary>
/// Gets a <see cref="Task"/> that will complete when this <see cref="Task"/> completes, or when the specified timeout expires.
/// </summary>
/// <param name="timeout">The timeout after which the <see cref="Task"/> should be faulted with a <see cref="TimeoutException"/> if it hasn't otherwise completed.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous wait. It may or may not be the same instance as the current instance.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.waitasync#system-threading-tasks-task-1-waitasync(system-timespan)")]
public static Task<TResult> WaitAsync<TResult>(
this Task<TResult> target,
TimeSpan timeout)
{
var cancelSource = new CancellationTokenSource();
try
{
return await target.WaitAsync(timeout, cancelSource.Token);
}
finally
{
cancelSource.Cancel();
cancelSource.Dispose();
}
}
TimeSpan timeout) =>
target.WaitAsync<TResult>(timeout, default);

/// <summary>
/// Gets a <see cref="Task"/> that will complete when this <see cref="Task"/> completes, when the specified timeout expires, or when the specified <see cref="CancellationToken"/> has cancellation requested.
/// </summary>
/// <param name="timeout">The timeout after which the <see cref="Task"/> should be faulted with a <see cref="TimeoutException"/> if it hasn't otherwise completed.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for a cancellation request.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous wait. It may or may not be the same instance as the current instance.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.waitasync#system-threading-tasks-task-1-waitasync(system-timespan-system-threading-cancellationtoken)")]
public static async Task<TResult> WaitAsync<TResult>(
this Task<TResult> target,
TimeSpan timeout,
CancellationToken cancellationToken)
{
var delayTask = Task.Delay(timeout, cancellationToken);
var completedTask = await Task.WhenAny(target, delayTask);
if (completedTask == delayTask)
{
throw new TimeoutException($"Execution did not complete within the time allotted {timeout.TotalMilliseconds} ms");
}

return await target;
await ((Task)target).WaitAsync(timeout, cancellationToken);
return target.Result;
}
}

#endif
#endif
Loading

0 comments on commit fd4b85f

Please sign in to comment.