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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user