mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-27 13:10:37 +08:00
ManagedSession: release the reader lock from a pooled state machine
ExitReaderOnCompletion registered the release as a continuation on the service call's task, so the caller's await was the second continuation and allocated the task's continuation list (~80 B per request). On .NET 6 and later the call is awaited in a pooled state machine that releases the lock before its own result completes; net48 keeps the continuation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8edf352b93
commit
4528d04676
@@ -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
|
||||
/// </summary>
|
||||
public AsyncReaderWriterLock()
|
||||
{
|
||||
#if !NET6_0_OR_GREATER
|
||||
m_releaseReader = ReleaseReader;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -187,13 +190,16 @@ namespace Opc.Ua.Client
|
||||
/// task for the same result.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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 <c>using</c> 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 <c>using</c> 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.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The result of the operation.</typeparam>
|
||||
public ValueTask<T> ExitReaderOnCompletion<T>(ValueTask<T> operation)
|
||||
@@ -203,11 +209,34 @@ namespace Opc.Ua.Client
|
||||
ReleaseReader();
|
||||
return operation;
|
||||
}
|
||||
#if NET6_0_OR_GREATER
|
||||
return ExitReaderAfterAsync(operation);
|
||||
#else
|
||||
Task<T> task = operation.AsTask();
|
||||
task.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted(m_releaseReader);
|
||||
return new ValueTask<T>(task);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Awaits <paramref name="operation"/> and releases the reader lock.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The result of the operation.</typeparam>
|
||||
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
|
||||
private async ValueTask<T> ExitReaderAfterAsync<T>(ValueTask<T> operation)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await operation.ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseReader();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 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();
|
||||
|
||||
Reference in New Issue
Block a user