mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-28 14:09:56 +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;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -96,7 +97,9 @@ namespace Opc.Ua.Client
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public AsyncReaderWriterLock()
|
public AsyncReaderWriterLock()
|
||||||
{
|
{
|
||||||
|
#if !NET6_0_OR_GREATER
|
||||||
m_releaseReader = ReleaseReader;
|
m_releaseReader = ReleaseReader;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -187,13 +190,16 @@ namespace Opc.Ua.Client
|
|||||||
/// task for the same result.
|
/// task for the same result.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The release is the first continuation registered on the operation's
|
/// The lock is free again by the time an awaiter of the returned task
|
||||||
/// task and runs synchronously when it completes, so the lock is normally
|
/// resumes, as with a <c>using</c> block around the awaited operation.
|
||||||
/// free again by the time an awaiter of the returned task resumes, as with
|
/// On .NET 6 and later the returned task awaits the operation in a pooled
|
||||||
/// a <c>using</c> block around the awaited operation. Only if the task
|
/// state machine, the only continuation of the operation, so this
|
||||||
/// completes while the continuation is being registered does the release
|
/// allocates nothing in the steady state; the returned task must be
|
||||||
/// run on the thread pool instead, shortly after. The delegate is cached,
|
/// awaited once, like any value task. Otherwise the release is the first
|
||||||
/// so this allocates nothing beyond the task's continuation list.
|
/// 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>
|
/// </remarks>
|
||||||
/// <typeparam name="T">The result of the operation.</typeparam>
|
/// <typeparam name="T">The result of the operation.</typeparam>
|
||||||
public ValueTask<T> ExitReaderOnCompletion<T>(ValueTask<T> operation)
|
public ValueTask<T> ExitReaderOnCompletion<T>(ValueTask<T> operation)
|
||||||
@@ -203,11 +209,34 @@ namespace Opc.Ua.Client
|
|||||||
ReleaseReader();
|
ReleaseReader();
|
||||||
return operation;
|
return operation;
|
||||||
}
|
}
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
return ExitReaderAfterAsync(operation);
|
||||||
|
#else
|
||||||
Task<T> task = operation.AsTask();
|
Task<T> task = operation.AsTask();
|
||||||
task.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted(m_releaseReader);
|
task.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted(m_releaseReader);
|
||||||
return new ValueTask<T>(task);
|
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>
|
/// <summary>
|
||||||
/// Asynchronously acquires the writer lock. Excludes both
|
/// Asynchronously acquires the writer lock. Excludes both
|
||||||
/// other writers and all readers for the duration the lock
|
/// 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;
|
private readonly Action m_releaseReader;
|
||||||
|
#endif
|
||||||
private bool m_isDisposed;
|
private bool m_isDisposed;
|
||||||
private readonly SemaphoreSlim m_writer = new(1, 1);
|
private readonly SemaphoreSlim m_writer = new(1, 1);
|
||||||
private readonly Lock m_state = new();
|
private readonly Lock m_state = new();
|
||||||
|
|||||||
Reference in New Issue
Block a user