diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8b6f10ac8..50dae742a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
Release Notes
====
+# 06-25-2024
+DotNext.Threading 5.8.0
+* Introduced `WaitAnyAsync` method to wait on a group of cancellation tokens
+* Added cancellation support for `WaitAsync` extension method for [WaitHandle](https://learn.microsoft.com/en-us/dotnet/api/system.threading.waithandle) class
+
# 06-20-2024
DotNext 5.7.0
* `Timestamp.ElapsedTicks` returns a value that is always greater than zero
diff --git a/README.md b/README.md
index 1a775ee2b..15a141b4e 100644
--- a/README.md
+++ b/README.md
@@ -44,31 +44,11 @@ All these things are implemented in 100% managed code on top of existing .NET AP
* [NuGet Packages](https://www.nuget.org/profiles/rvsakno)
# What's new
-Release Date: 06-20-2024
+Release Date: 06-25-2024
-DotNext 5.7.0
-* `Timestamp.ElapsedTicks` returns a value that is always greater than zero
-* Fixed hidden copies of value types caused by **in** modifier
-* Added support of [TimeProvider](https://learn.microsoft.com/en-us/dotnet/api/system.timeprovider) to `Timestamp` and `Timeout` types
-
-DotNext.Metaprogramming 5.7.0
-* Updated dependencies
-
-DotNext.Unsafe 5.7.0
-* Updated dependencies
-
-DotNext.Threading 5.7.0
-* Fixed [241](https://github.com/dotnet/dotNext/issues/241)
-* Introduced API for implementing leases, see `DotNext.Threading.Leases` namespace
-
-DotNext.IO 5.7.0
-* Various performance improvements
-
-DotNext.Net.Cluster 5.7.0
-* Fixed [242](https://github.com/dotnet/dotNext/issues/242)
-
-DotNext.AspNetCore.Cluster 5.7.0
-* Fixed [242](https://github.com/dotnet/dotNext/issues/242)
+DotNext.Threading 5.8.0
+* Introduced `WaitAnyAsync` method to wait on a group of cancellation tokens
+* Added cancellation support for `WaitAsync` extension method for [WaitHandle](https://learn.microsoft.com/en-us/dotnet/api/system.threading.waithandle) class
Changelog for previous versions located [here](./CHANGELOG.md).
diff --git a/src/DotNext.Tests/Threading/AsyncBridgeTests.cs b/src/DotNext.Tests/Threading/AsyncBridgeTests.cs
index 38010dd22..de479e93b 100644
--- a/src/DotNext.Tests/Threading/AsyncBridgeTests.cs
+++ b/src/DotNext.Tests/Threading/AsyncBridgeTests.cs
@@ -26,11 +26,25 @@ public static async Task WaitForSignal()
await ev.WaitAsync(DefaultTimeout);
}
+ [Fact]
+ public static async Task CancelWaitForSignal()
+ {
+ using var ev = new ManualResetEvent(false);
+ using var cts = new CancellationTokenSource();
+
+ var task = ev.WaitAsync(cts.Token).AsTask();
+ cts.Cancel();
+
+ var e = await ThrowsAsync(Func.Constant(task));
+ Equal(cts.Token, e.CancellationToken);
+ }
+
[Fact]
public static async Task AlreadySignaled()
{
using var ev = new ManualResetEvent(true);
True(await ev.WaitAsync(DefaultTimeout));
+ True(ev.WaitAsync().IsCompletedSuccessfully);
}
[Fact]
@@ -68,4 +82,43 @@ public static async Task CancellationTokenAwaitCornerCases()
await new CancellationToken(true).WaitAsync();
await ThrowsAsync(new CancellationToken(false).WaitAsync().AsTask);
}
+
+ [Fact]
+ public static async Task WaitForCancellationSingleToken()
+ {
+ using var cts = new CancellationTokenSource();
+ var task = AsyncBridge.WaitAnyAsync([cts.Token]);
+ False(task.IsCompletedSuccessfully);
+
+ cts.Cancel();
+ Equal(cts.Token, await task);
+ }
+
+ [Fact]
+ public static async Task WaitForCancellationTwoTokens()
+ {
+ using var cts1 = new CancellationTokenSource();
+ using var cts2 = new CancellationTokenSource();
+ var task = AsyncBridge.WaitAnyAsync([cts1.Token, cts2.Token]);
+ False(task.IsCompletedSuccessfully);
+
+ cts2.Cancel();
+ cts1.Cancel();
+ Equal(cts2.Token, await task);
+ }
+
+ [Fact]
+ public static async Task WaitForCancellationMultipleTokens()
+ {
+ using var cts1 = new CancellationTokenSource();
+ using var cts2 = new CancellationTokenSource();
+ using var cts3 = new CancellationTokenSource();
+ var task = AsyncBridge.WaitAnyAsync([cts1.Token, cts2.Token, cts3.Token]);
+ False(task.IsCompletedSuccessfully);
+
+ cts3.Cancel();
+ cts2.Cancel();
+ cts1.Cancel();
+ Equal(cts3.Token, await task);
+ }
}
\ No newline at end of file
diff --git a/src/DotNext.Threading/DotNext.Threading.csproj b/src/DotNext.Threading/DotNext.Threading.csproj
index b6f6e7417..7adf1a788 100644
--- a/src/DotNext.Threading/DotNext.Threading.csproj
+++ b/src/DotNext.Threading/DotNext.Threading.csproj
@@ -7,7 +7,7 @@
truetruenullablePublicOnly
- 5.7.0
+ 5.8.0.NET Foundation and Contributors.NEXT Family of Libraries
diff --git a/src/DotNext.Threading/Threading/AsyncBridge.CancellationToken.cs b/src/DotNext.Threading/Threading/AsyncBridge.CancellationToken.cs
index 33f338e67..e6bce1c20 100644
--- a/src/DotNext.Threading/Threading/AsyncBridge.CancellationToken.cs
+++ b/src/DotNext.Threading/Threading/AsyncBridge.CancellationToken.cs
@@ -1,4 +1,6 @@
+using System.Buffers;
using System.Collections.Concurrent;
+using System.Runtime.InteropServices;
using Debug = System.Diagnostics.Debug;
using Unsafe = System.Runtime.CompilerServices.Unsafe;
@@ -38,6 +40,127 @@ internal void Return(CancellationTokenValueTask vt)
Add(vt);
}
}
+
+ private abstract class CancellationTokenCompletionSource : TaskCompletionSource
+ {
+ protected static readonly Action