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();