diff --git a/src/Opc.Ua.Client/Utils/AsyncReaderWriterLock.cs b/src/Opc.Ua.Client/Utils/AsyncReaderWriterLock.cs
index b4395d0b59..8ead0d9a6c 100644
--- a/src/Opc.Ua.Client/Utils/AsyncReaderWriterLock.cs
+++ b/src/Opc.Ua.Client/Utils/AsyncReaderWriterLock.cs
@@ -28,6 +28,7 @@
* ======================================================================*/
using System;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@@ -96,7 +97,9 @@ namespace Opc.Ua.Client
///
public AsyncReaderWriterLock()
{
+#if !NET6_0_OR_GREATER
m_releaseReader = ReleaseReader;
+#endif
}
///
@@ -187,13 +190,16 @@ namespace Opc.Ua.Client
/// task for the same result.
///
///
- /// The release is the first continuation registered on the operation's
- /// task and runs synchronously when it completes, so the lock is normally
- /// free again by the time an awaiter of the returned task resumes, as with
- /// a using block around the awaited operation. Only if the task
- /// completes while the continuation is being registered does the release
- /// run on the thread pool instead, shortly after. The delegate is cached,
- /// so this allocates nothing beyond the task's continuation list.
+ /// The lock is free again by the time an awaiter of the returned task
+ /// resumes, as with a using block around the awaited operation.
+ /// On .NET 6 and later the returned task awaits the operation in a pooled
+ /// state machine, the only continuation of the operation, so this
+ /// allocates nothing in the steady state; the returned task must be
+ /// awaited once, like any value task. Otherwise the release is the first
+ /// continuation registered on the operation's task and the caller's await
+ /// the second, which allocates the task's continuation list; only if the
+ /// task completes while the release is being registered does it run on
+ /// the thread pool, shortly after.
///
/// The result of the operation.
public ValueTask ExitReaderOnCompletion(ValueTask operation)
@@ -203,11 +209,34 @@ namespace Opc.Ua.Client
ReleaseReader();
return operation;
}
+#if NET6_0_OR_GREATER
+ return ExitReaderAfterAsync(operation);
+#else
Task task = operation.AsTask();
task.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted(m_releaseReader);
return new ValueTask(task);
+#endif
}
+#if NET6_0_OR_GREATER
+ ///
+ /// Awaits and releases the reader lock.
+ ///
+ /// The result of the operation.
+ [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
+ private async ValueTask ExitReaderAfterAsync(ValueTask operation)
+ {
+ try
+ {
+ return await operation.ConfigureAwait(false);
+ }
+ finally
+ {
+ ReleaseReader();
+ }
+ }
+#endif
+
///
/// Asynchronously acquires the writer lock. Excludes both
/// other writers and all readers for the duration the lock
@@ -390,7 +419,9 @@ namespace Opc.Ua.Client
}
}
+#if !NET6_0_OR_GREATER
private readonly Action m_releaseReader;
+#endif
private bool m_isDisposed;
private readonly SemaphoreSlim m_writer = new(1, 1);
private readonly Lock m_state = new();