feat: HistorianClient.OpenSessionAsync(kind) — open a reusable HistorianSession

This commit is contained in:
Joseph Doherty
2026-06-25 03:07:02 -04:00
parent d2b00fcda5
commit 15da8516ba
@@ -347,6 +347,41 @@ public sealed class HistorianClient : IAsyncDisposable
: new HistorianWcfTagWriteOrchestrator(_options).RenameTagsAsync(pairs, cancellationToken);
}
/// <summary>
/// Opens a reusable authenticated <see cref="HistorianSession"/> over the 2023 R2 gRPC transport.
/// The caller owns the session and must dispose it. Reusing the session across ops amortizes the auth
/// handshake; the server idle-expires it in ~20-25s, so keep it warm (HistorianSession.PingAsync) or
/// re-open. RemoteGrpc only.
/// </summary>
public async Task<HistorianSession> OpenSessionAsync(HistorianSessionKind kind, CancellationToken cancellationToken = default)
{
if (_options.Transport != HistorianTransport.RemoteGrpc)
{
throw new ProtocolEvidenceMissingException(
"HistorianSession is only supported over the 2023 R2 RemoteGrpc transport.");
}
return await Task.Run(() =>
{
uint mode = kind == HistorianSessionKind.WriteEnabled
? HistorianWcfAuthChainHelper.NativeIntegratedWriteEnabledConnectionMode
: HistorianWcfAuthChainHelper.NativeIntegratedReadOnlyConnectionMode;
Grpc.HistorianGrpcConnection connection = Grpc.HistorianGrpcChannelFactory.Create(_options);
try
{
Grpc.HistorianGrpcHandshake.Session session =
Grpc.HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, connectionMode: mode);
return new HistorianSession(connection, session, _options, kind);
}
catch
{
connection.Dispose(); // don't leak the channel if the handshake fails
throw;
}
}, cancellationToken).ConfigureAwait(false);
}
public ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;