Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion project-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "task-chaining",
"description": "Extension methods to System.Threading.Task to allow Promise-like chaining",
"title": "TaskChaining",
"version": "2.19.2",
"version": "2.20.0",
"ciEnvironment": {
"variables": [
{
Expand Down
29 changes: 24 additions & 5 deletions src/TaskExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -433,16 +433,35 @@ public static Task<T> CatchWhen<T, TException>(this Task<T> task, Func<TExceptio
? onFaulted((TException) ex)
: task
);


/// <summary>Creates a cancellable task that completes after a specified time interval.</summary>
/// <param name="task">The task.</param>
/// <param name="delayInterval">The time span to wait before completing the returned task, or <see langword="TimeSpan.FromMilliseconds(-1)" /> to wait indefinitely.</param>
/// <param name="cancellationToken">A cancellation token to observe while waiting for the task to complete.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="delayInterval" /> represents a negative time interval other than <see langword="TimeSpan.FromMilliseconds(-1)" />.
///
/// -or-
///
/// The <paramref name="delayInterval" /> argument's <see cref="P:System.TimeSpan.TotalMilliseconds" /> property is greater than 4294967294 on .NET 6 and later versions, or <see cref="F:System.Int32.MaxValue">Int32.MaxValue</see> on all previous versions.</exception>
/// <exception cref="T:System.Threading.Tasks.TaskCanceledException">The task has been canceled.</exception>
/// <exception cref="T:System.ObjectDisposedException">The provided <paramref name="cancellationToken" /> has already been disposed.</exception>
/// <returns>A task that represents the time delay.</returns>
public static Task<T> Delay<T>(
this Task<T> task,
TimeSpan delayInterval,
CancellationToken cancellationToken = default
) => task.ContinueWith(continuationTask =>
{
return Task.Delay(delayInterval, cancellationToken)
.ContinueWith(delayTask => continuationTask.Result);
}).Unwrap();
{
return Task.Delay(delayInterval, cancellationToken)
.ContinueWith(_ => continuationTask.IsFaulted
? Task.FromException<T>(PotentiallyUnwindException(continuationTask.Exception!))
: Task.FromResult(continuationTask.Result),
cancellationToken
);
},
cancellationToken
).Unwrap().Unwrap();

/// <summary>
/// Faults a <see cref="Task{T}"/> with a provided <see cref="Exception"/>.
Expand Down
44 changes: 44 additions & 0 deletions tests/unit/DelayTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using RLC.TaskChaining;
using Xunit;

namespace RLC.TaskChainingTests;

public class DelayTests
{
[Fact]
public async Task ItShouldWaitTheConfiguredTime()
{
Stopwatch testStopWatch = new();
int testDelayTimeMilliseconds = 15;

testStopWatch.Start();

await Task.FromResult(1).Delay(TimeSpan.FromMilliseconds(testDelayTimeMilliseconds));

testStopWatch.Stop();

Assert.True(testStopWatch.ElapsedMilliseconds >= testDelayTimeMilliseconds);
}

[Fact]
public async Task ItShouldEnsureRetryExceptionsAreNotWrapped()
{
Func<int, Task<int>> testFunc = _ => Task.FromException<int>(new RetryException(1, new ArgumentNullException()));
Exception? actualValue = null;

try
{
await testFunc(1)
.Delay(TimeSpan.Zero);
}
catch (RetryException exception)
{
actualValue = exception.InnerException!;
}

Assert.IsType<ArgumentNullException>(actualValue);
}
}
7 changes: 7 additions & 0 deletions tests/unit/xunit.runner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"maxParallelThreads": 1,
"parallelizeAssembly": false,
"parallelizeTestCollections": false,
"shadowCopy": false
}