Verbesserungen: Thread-Sicherheit, Logging, WCF-Updates

Die Änderungen umfassen:
- Einführung von `ReaderWriterLockSlim` für Thread-Sicherheit.
- Optimierung von `IsOnline` mit atomaren Operationen.
- Verbesserte Reconnect-Logik und Fehlerbehandlung.
- Neue Methoden für Datenbankoperationen (`GetDatatableByName` etc.).
- Gemeinsame `ChannelFactory` zur Ressourcenschonung.
- Logging-Verbesserungen mit präziseren Meldungen und Symbolen.
- Aktualisierte WCF-Bindings für höhere Verbindungszahlen und kürzere Timeouts.
- Aktualisierte Service-Referenzen mit asynchronen Methoden.
- Bereinigung von veralteten Methoden und Eigenschaften.
- Assembly-Version auf `2.8.0.0` aktualisiert.
This commit is contained in:
Developer01
2026-10-13 12:06:50 +02:00
parent aad2f01490
commit 3eb6314ba8
9 changed files with 4244 additions and 659 deletions

View File

@@ -22,8 +22,45 @@ Public Class Client
' Runtime Variables
Private _ServerAddress As ServerAddressStruct
Private _ClientConfig As GlobalStateClientConfiguration
Private ReadOnly _CachedTablesLock As New ReaderWriterLockSlim()
Private _CachedTables As New List(Of String)
Private _IsOnline As Boolean
Public ReadOnly Property CachedTables As List(Of String)
Get
_CachedTablesLock.EnterReadLock()
Try
Return _CachedTables.ToList() ' Snapshot zurückgeben
Finally
_CachedTablesLock.ExitReadLock()
End Try
End Get
End Property
' Hilfsmethode – intern für alle Schreibzugriffe verwenden
Private Sub SetCachedTables(pTables As IEnumerable(Of String))
_CachedTablesLock.EnterWriteLock()
Try
_CachedTables = pTables.ToList()
Finally
_CachedTablesLock.ExitWriteLock()
End Try
End Sub
' Ersetzt: Private _IsOnline As Boolean
Private _IsOnline As Integer = 0 ' 0 = offline, 1 = online (Interlocked-kompatibel)
Public ReadOnly Property IsOnline As Boolean
Get
Return Interlocked.CompareExchange(_IsOnline, 0, 0) = 1
End Get
End Property
' Setter-Hilfsmethoden für alle Schreibstellen:
Private Sub SetOnline()
Interlocked.Exchange(_IsOnline, 1)
End Sub
Private Sub SetOffline()
Interlocked.Exchange(_IsOnline, 0)
End Sub
' Verhindert parallele Reconnect-Versuche (0 = frei, 1 = läuft)
Private _IsReconnecting As Integer = 0
Private _Channel As IEDMIServiceChannel
@@ -56,14 +93,14 @@ Public Class Client
Protected Overridable Sub Dispose(disposing As Boolean)
If _Disposed Then Return
If disposing Then
_IsOnline = False ' ← Reconnect-Guard deaktivieren
SetOffline()
UpdateTimer.Stop()
UpdateTimer.Dispose()
' Auf laufenden Reconnect warten
Dim oTimeout = DateTime.Now.AddSeconds(5)
While _IsReconnecting = 1 AndAlso DateTime.Now < oTimeout
Task.Delay(100).Wait()
Thread.Sleep(100)
End While
If _Channel IsNot Nothing Then
@@ -71,6 +108,8 @@ Public Class Client
_Channel = Nothing
End If
_CachedTablesLock.Dispose() ' ← NEU: Lock freigeben
Dim oRemoved As ClientStatus = Nothing
_ActiveClients.TryRemove(_ClientId, oRemoved)
Logger.Debug("Client [{0}] unregistered. Total active clients: [{1}]", _ClientName, _ActiveClients.Count)
@@ -79,11 +118,6 @@ Public Class Client
End Sub
' Public Variables
Public ReadOnly Property CachedTables
Get
Return _CachedTables
End Get
End Property
Public ReadOnly Property ClientConfig As GlobalStateClientConfiguration
Get
@@ -94,13 +128,6 @@ Public Class Client
Return _ClientConfig
End Get
End Property
Public ReadOnly Property IsOnline As Boolean
Get
Return _IsOnline
End Get
End Property
Public ReadOnly Property ServerAddress As String
Get
Return $"{_ServerAddress.Host}:{_ServerAddress.Port}"
@@ -197,14 +224,14 @@ Public Class Client
' CachedTables sofort befüllen – nicht auf den ersten Timer-Tick warten
Try
Dim oTables As String() = _Channel.GetCachedTables()
_CachedTables = oTables.Select(Function(t) t.ToUpper).ToList()
Logger.Debug("✔ CachedTables loaded on connect: [{0}]", _CachedTables.Count)
SetCachedTables(oTables.Select(Function(t) t.ToUpper()))
Logger.Debug("✔ CachedTables loaded on connect: [{0}]", CachedTables.Count)
Catch ex As Exception
Logger.Warn("⚠ CachedTables could not be loaded on connect: {0}", ex.Message)
_CachedTables = New List(Of String)
SetCachedTables(Enumerable.Empty(Of String)())
End Try
_IsOnline = True ' ← vor GetStatus()
SetOnline()
UpdateTimer.Start()
' In Registry eintragen – jetzt mit korrektem IsOnline=True
@@ -213,7 +240,7 @@ Public Class Client
Return True
Catch ex As Exception
_IsOnline = False
SetOffline()
Logger.Error(ex)
Return False
End Try
@@ -243,7 +270,7 @@ Public Class Client
_Channel = ChannelManager.GetChannel()
_Channel.Open()
_IsOnline = True
SetOnline()
If _ActiveClients.ContainsKey(_ClientId) Then
_ActiveClients(_ClientId) = GetStatus()
@@ -253,7 +280,7 @@ Public Class Client
Return
Catch ex As Exception
Logger.Warn("⚠ Reconnect failed after [{0}]s: {1}", oDelay, ex.Message)
_IsOnline = False
SetOffline()
End Try
Next
@@ -266,53 +293,51 @@ Public Class Client
Private Async Sub UpdateTimer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles UpdateTimer.Elapsed
Try
If _Channel Is Nothing OrElse _Channel.State <> CommunicationState.Opened Then
Logger.Warn("⚠ UpdateTimer skipped: Channel not in Opened state [{0}]", If(_Channel IsNot Nothing, _Channel.State.ToString(), "null"))
_IsOnline = False
' ← Snapshot VOR dem Await – schützt gegen _Channel = Nothing nach Resume
Dim oChannel = _Channel
If oChannel Is Nothing OrElse oChannel.State <> CommunicationState.Opened Then
Logger.Warn("⚠ UpdateTimer skipped: Channel not in Opened state [{0}]",
If(oChannel IsNot Nothing, oChannel.State.ToString(), "null"))
SetOffline()
Reconnect(sender, e)
Return
End If
If Not _IsOnline Then
If Not IsOnline Then
Logger.Debug("UpdateTimer skipped: Client is offline.")
Return
End If
Dim oTables As String() = Await _Channel.GetCachedTablesAsync()
' ◄ NullReferenceException-Schutz: Service kann Nothing zurückgeben
Dim oTables As String() = Await oChannel.GetCachedTablesAsync() ' ← oChannel statt _Channel
If oTables Is Nothing Then
Logger.Warn("⚠ UpdateTimer: GetCachedTablesAsync returned Nothing.")
_CachedTables = New List(Of String)
SetCachedTables(Enumerable.Empty(Of String)())
Return
End If
_CachedTables = oTables.
Select(Function(table) table.ToUpper).
ToList()
SetCachedTables(oTables.Select(Function(table) table.ToUpper()))
' ← oChannel.State statt _Channel.State
Logger.Info("✔ Channel state: [{0}] | Active connections: [{1}] | CachedTables: [{2}]",
_Channel.State,
Channel.ActiveConnections,
_CachedTables.Count)
oChannel.State, Channel.ActiveConnections, CachedTables.Count)
If _ActiveClients.ContainsKey(_ClientId) Then
_ActiveClients(_ClientId) = GetStatus()
End If
Catch ex As TimeoutException
' Stale TCP connection (z.B. Firewall/Citrix Idle-Timeout)
Logger.Warn("⚠ UpdateTimer: TCP connection lost (idle timeout / firewall). Triggering reconnect...")
_IsOnline = False
Logger.Warn("⚠ UpdateTimer: TCP connection lost. Triggering reconnect...")
SetOffline()
Reconnect()
Catch ex As CommunicationException
Logger.Warn("⚠ UpdateTimer: Channel faulted. Triggering reconnect...")
_IsOnline = False
SetOffline()
Reconnect()
Catch ex As Exception
Logger.Warn("⚠ Update of CachedTable was not successful!")
Logger.Error(ex)
_CachedTables = New List(Of String)
SetCachedTables(Enumerable.Empty(Of String)())
End Try
End Sub
@@ -626,6 +651,59 @@ Public Class Client
End Function
#Region "GetDatatable"
Public Function GetDatatableByName(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As TableResult
EnsureAvailable()
Try
Return _Channel.ReturnDatatableFromCache(DatatableName, FilterExpression, SortByColumn)
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableByName))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableByName))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableByName))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Function GetScalarValueFromIDB(pSQL As String) As GetScalarValueResponse
EnsureAvailable()
Try
Return _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Function ExecuteNonQueryFromIDB(pSQL As String) As ExecuteNonQueryResponse
EnsureAvailable()
Try
Return _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Function GetDatatableFromIDB(pSQL As String) As GetDatatableResponse
EnsureAvailable()
Try
@@ -633,6 +711,8 @@ Public Class Client
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As TimeoutException ' ← NEU
HandleCommunicationError(ex, NameOf(GetDatatableFromIDB))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDB))
Catch ex As CommunicationException
@@ -643,6 +723,7 @@ Public Class Client
End Try
End Function
' ── GetDatatable ──────────────────────────────────────────
Public Function GetDatatableFromECM(pSQL As String) As GetDatatableResponse
EnsureAvailable()
Try
@@ -650,6 +731,8 @@ Public Class Client
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableFromECM))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromECM))
Catch ex As CommunicationException
@@ -668,6 +751,8 @@ Public Class Client
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnection))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnection))
Catch ex As CommunicationException
@@ -685,6 +770,8 @@ Public Class Client
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDBAsync))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDBAsync))
Catch ex As CommunicationException
@@ -703,6 +790,8 @@ Public Class Client
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableFromECMAsync))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromECMAsync))
Catch ex As CommunicationException
@@ -721,6 +810,8 @@ Public Class Client
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnectionAsync))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnectionAsync))
Catch ex As CommunicationException
@@ -730,26 +821,8 @@ Public Class Client
Throw
End Try
End Function
#End Region
#Region "GetScalarValue"
Public Function GetScalarValueFromIDB(pSQL As String) As GetScalarValueResponse
EnsureAvailable()
Try
Return _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
' ── GetScalarValue ─────────────────────────────────────────
Public Function GetScalarValueFromECM(pSQL As String) As GetScalarValueResponse
EnsureAvailable()
Try
@@ -757,6 +830,8 @@ Public Class Client
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECM))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECM))
Catch ex As CommunicationException
@@ -775,6 +850,8 @@ Public Class Client
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnection))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnection))
Catch ex As CommunicationException
@@ -792,6 +869,8 @@ Public Class Client
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDBAsync))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDBAsync))
Catch ex As CommunicationException
@@ -809,6 +888,8 @@ Public Class Client
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECMAsync))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECMAsync))
Catch ex As CommunicationException
@@ -827,6 +908,8 @@ Public Class Client
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnectionAsync))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnectionAsync))
Catch ex As CommunicationException
@@ -836,26 +919,8 @@ Public Class Client
Throw
End Try
End Function
#End Region
#Region "ExecuteNonQuery"
Public Function ExecuteNonQueryFromIDB(pSQL As String) As ExecuteNonQueryResponse
EnsureAvailable()
Try
Return _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
' ── ExecuteNonQuery ────────────────────────────────────────
Public Function ExecuteNonQueryFromECM(pSQL As String) As ExecuteNonQueryResponse
EnsureAvailable()
Try
@@ -863,6 +928,8 @@ Public Class Client
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECM))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECM))
Catch ex As CommunicationException
@@ -881,6 +948,8 @@ Public Class Client
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnection))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnection))
Catch ex As CommunicationException
@@ -898,6 +967,8 @@ Public Class Client
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDBAsync))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDBAsync))
Catch ex As CommunicationException
@@ -915,6 +986,8 @@ Public Class Client
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECMAsync))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECMAsync))
Catch ex As CommunicationException
@@ -933,6 +1006,8 @@ Public Class Client
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnectionAsync))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnectionAsync))
Catch ex As CommunicationException
@@ -942,45 +1017,26 @@ Public Class Client
Throw
End Try
End Function
' ── Sonstige ───────────────────────────────────────────────
Public Async Function GetDatatableByNameAsync(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As Task(Of TableResult)
EnsureAvailable()
Try
Return Await _Channel.ReturnDatatableFromCacheAsync(DatatableName, FilterExpression, SortByColumn)
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableByNameAsync))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableByNameAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableByNameAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
#End Region
Public Function GetDatatableByName(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As TableResult
EnsureAvailable()
Try
Return _Channel.ReturnDatatableFromCache(DatatableName, FilterExpression, SortByColumn)
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableByName))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableByName))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Async Function GetDatatableByNameAsync(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As Task(Of TableResult)
EnsureAvailable()
Try
Return Await _Channel.ReturnDatatableFromCacheAsync(DatatableName, FilterExpression, SortByColumn)
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableByNameAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableByNameAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
''' <summary>
''' Return infos about a file object
''' </summary>
''' <param name="UserId"></param>
''' <param name="ObjectId"></param>
''' <returns></returns>
Public Function GetDocumentInfo(UserId As Long, ObjectId As Long) As DocumentInfo
Try
Dim oResponse As DocumentInfoResponse = _Channel.GetFileInfoByObjectId(New DocumentInfoRequest With {
@@ -1056,27 +1112,25 @@ Public Class Client
Throw New InvalidOperationException("Channel is not initialized. Call Connect() first.")
End If
If Not _IsOnline Then
' Kurze Grace-Period abwarten, damit Reconnect() seinen _IsReconnecting-Flag setzen kann
Dim oGraceLimit = DateTime.Now.AddMilliseconds(600)
While DateTime.Now < oGraceLimit AndAlso _IsReconnecting = 0 AndAlso Not _IsOnline
Task.Delay(100).Wait()
End While
If Not IsOnline Then
' Wenn Reconnect aktiv läuft: kurze Grace-Period (max 1s) statt 10s
If _IsReconnecting = 1 Then
Dim oGraceLimit = DateTime.Now.AddMilliseconds(1000)
While DateTime.Now < oGraceLimit AndAlso Not IsOnline
Thread.Sleep(100)
End While
End If
' Jetzt auf aktiven Reconnect warten
Dim oWaitLimit = DateTime.Now.AddSeconds(10)
While Not _IsOnline AndAlso DateTime.Now < oWaitLimit AndAlso _IsReconnecting = 1
Task.Delay(500).Wait()
Logger.Debug("EnsureAvailable: Waiting for reconnect... Channel=[{0}]", _Channel.State.ToString())
End While
If Not _IsOnline Then
Throw New InvalidOperationException("Client is offline.")
' Danach sofort werfen – WCF-Timeout nicht weiter aufbrauchen
If Not IsOnline Then
Throw New InvalidOperationException(
$"Client is offline. Reconnect in progress=[{_IsReconnecting = 1}].")
End If
End If
If _Channel.State <> CommunicationState.Opened Then
Throw New InvalidOperationException($"Channel is not available. State=[{_Channel.State}]")
Throw New InvalidOperationException(
$"Channel is not available. State=[{_Channel.State}]")
End If
End Sub
''' <summary>
@@ -1084,6 +1138,8 @@ Public Class Client
''' </summary>
Private Sub HandleCommunicationError(ex As Exception, callerName As String)
Select Case True
Case TypeOf ex Is TimeoutException
Logger.Warn("⚠ TimeoutException in [{0}]. Channel may be stale. Triggering reconnect...", callerName)
Case TypeOf ex Is EndpointNotFoundException
Logger.Warn("⚠ Service not reachable (EndpointNotFoundException in [{0}]). Triggering reconnect...", callerName)
Case TypeOf ex Is CommunicationException
@@ -1093,7 +1149,7 @@ Public Class Client
Logger.Error(ex)
End Select
_IsOnline = False
SetOffline()
Reconnect()
Throw ex
End Sub
@@ -1190,15 +1246,16 @@ Public Class Client
''' Returns a detailed status snapshot of the current client state
''' </summary>
Public Function GetStatus() As ClientStatus
Dim oTables = CachedTables ' ← Property nutzen: enthält Read-Lock + Snapshot
Return New ClientStatus() With {
.IsOnline = _IsOnline,
.ClientId = _ClientId, ' ← Neu
.IsOnline = IsOnline, ' ← Property statt Feld
.ClientId = _ClientId,
.ClientName = _ClientName,
.ServerAddress = Me.ServerAddress,
.ChannelState = If(_Channel IsNot Nothing, _Channel.State.ToString(), "null"),
.ActiveConnections = Channel.ActiveConnections,
.CachedTableCount = _CachedTables.Count,
.CachedTables = _CachedTables.ToList(),
.CachedTableCount = oTables.Count, ' ← aus Snapshot
.CachedTables = oTables, ' ← aus Snapshot
.HasClientConfig = _ClientConfig IsNot Nothing,
.IsDisposed = _Disposed,
.TimerEnabled = UpdateTimer.Enabled,