Files
TaskFlow/app/TaskFlow/frmAdmin_AppserviceTest.vb
Developer01 4f09e678d2 Neue Projekte & Optimierungen in TaskFlow
Es wurden zwei neue Projekte (`EDMI.API` und `EDMIService`) zur Solution hinzugefügt und in die Build-Konfiguration integriert.
Die Benutzeroberfläche (`frmAdmin_AppserviceTest.Designer.vb`) wurde angepasst, einschließlich Änderungen an Button-Größen, Positionen und `NumericUpDown1`-Werten.

Die Logik für Benutzer- und Sitzungsinformationen wurde verbessert, um Null-Checks und die Anzeige kürzlich aktiver Benutzer zu unterstützen.
Der Lasttest wurde optimiert: Verwendung von `ConcurrentBag`, Einführung von `IProgress` für UI-Feedback und Begrenzung der Parallelität.

Beim Schließen des Validators (`frmMain.vb`) wurde ein DevExpress-Overlay hinzugefügt, um den Ladezustand anzuzeigen, und die Performance beim Neuaufbau des Grids verbessert.

Allgemeine Verbesserungen umfassen explizite Typkonvertierungen, bessere Fehlerbehandlung und Logging.
2026-10-12 15:50:31 +02:00

300 lines
15 KiB
VB.net
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Imports System.Collections.Concurrent
Imports System.Threading
Imports System.Threading.Tasks
Imports DigitalData.Modules.EDMI.API
Imports DigitalData.Modules.EDMI.API.Constants
Imports DigitalData.Modules.EDMI.API.DatabaseWithFallback
Imports DigitalData.Modules.Logging
Public Class frmAdmin_AppserviceTest
' ── Lasttest-Felder ────────────────────────────────────────────────────
Private _loadClients As List(Of Client) ' Typisiert auf Client statt Object
Private _loadTimer As System.Windows.Forms.Timer
Private _loadLog As New ConcurrentQueue(Of String)
Private _loadCts As CancellationTokenSource
Private _timerTick As Integer = 0
Private LOAD_CLIENT_COUNT As Integer = 10
Private Shared ReadOnly _rnd As New Random() ' ◄ ergänzen
Private Const RECENT_USER_MAX_AGE_MINUTES As Integer = 15 ' ◄ NEU – muss mit Service-Wert übereinstimmen
Private Sub SimpleButton1_Click(sender As Object, e As EventArgs) Handles SimpleButton1.Click
' Status abrufen
Dim oStatus = _EDMIClient.GetStatus()
If oStatus Is Nothing Then
LOGGER.Warn("GetStatus() returned null. AppService is offline or unreachable.")
Return
Else
LOGGER.Info(oStatus.ToString())
End If
' Einzelne Felder
MemoEdit1.Text = ""
MemoEdit1.Text &= $"Online-Status: {oStatus.IsOnline}" & Environment.NewLine
MemoEdit1.Text &= $"Channel-State: {oStatus.ChannelState}" & Environment.NewLine
MemoEdit1.Text &= $"Anzahl Verbindungen: {oStatus.ActiveConnections}" & Environment.NewLine
MemoEdit1.Text &= $"Cached Tables ({oStatus.CachedTableCount})" & Environment.NewLine
If oStatus.IsOnline And oStatus.ChannelState = "Opened" And oStatus.ActiveConnections > 0 And oStatus.CachedTableCount > 1 Then
lblServiceOnline.Visible = True
lblAppServiceOffline.Visible = False
Else
lblServiceOnline.Visible = False
lblAppServiceOffline.Visible = True
End If
Dim oClients = _EDMIClient.GetAllClientStatus()
MemoEdit1.Text &= "=============== Geöffnete Clients ===============" & Environment.NewLine
For Each oStatus In oClients
MemoEdit1.Text &= oStatus.ToString() & Environment.NewLine
LOGGER.Info(oStatus.ToString())
Next
Dim oStatus2 = _EDMIClient.GetServiceStatus()
If oStatus2 Is Nothing Then
LOGGER.Warn("GetServiceStatus() returned null. AppService is offline or unreachable.")
Return
Else
LOGGER.Info($"ServiceStatus: Sessions={oStatus2.ActiveSessionCount}")
End If
MemoEdit1.Text &= $"Aktive Sessions: {oStatus2.ActiveSessionCount}" & Environment.NewLine
MemoEdit1.Text &= "─────────────────────────────" & Environment.NewLine
MemoEdit1.Text &= "Aktive Benutzer:" & Environment.NewLine
If oStatus2.ActiveUsers IsNot Nothing AndAlso oStatus2.ActiveUsers.Count > 0 Then
For Each oUser In oStatus2.ActiveUsers
MemoEdit1.Text &= $" • {oUser}" & Environment.NewLine
Next
Else
MemoEdit1.Text &= " (keine)" & Environment.NewLine
End If
MemoEdit1.Text &= "─────────────────────────────" & Environment.NewLine
MemoEdit1.Text &= "Sessions (Id → Benutzer):" & Environment.NewLine
If oStatus2.ActiveSessions IsNot Nothing AndAlso oStatus2.ActiveSessions.Count > 0 Then
For Each oPair In oStatus2.ActiveSessions
MemoEdit1.Text &= $" {oPair.Key.Substring(0, Math.Min(8, oPair.Key.Length))}… → {oPair.Value}" & Environment.NewLine
Next
Else
MemoEdit1.Text &= " (keine)" & Environment.NewLine
End If
MemoEdit1.Text &= "─────────────────────────────" & Environment.NewLine
MemoEdit1.Text &= $"Kürzlich aktive Benutzer (letzte {RECENT_USER_MAX_AGE_MINUTES} Min.):" & Environment.NewLine
If oStatus2.RecentUsers IsNot Nothing AndAlso oStatus2.RecentUsers.Count > 0 Then
For Each oRecentUser In oStatus2.RecentUsers
MemoEdit1.Text &= $" • {oRecentUser}" & Environment.NewLine
Next
Else
MemoEdit1.Text &= " (keine)" & Environment.NewLine
End If
End Sub
Private Sub SimpleButton2_Click(sender As Object, e As EventArgs) Handles btnLoadStart.Click
If _loadTimer IsNot Nothing AndAlso _loadTimer.Enabled Then
MessageBox.Show("Lasttest läuft bereits.", "Hinweis", MessageBoxButtons.OK, MessageBoxIcon.Information)
Return
End If
LOAD_CLIENT_COUNT = CInt(NumericUpDown1.Value)
_loadLog = New ConcurrentQueue(Of String)
_loadCts = New CancellationTokenSource()
_timerTick = 0
btnLoadStart.Enabled = False
btnLoadStop.Enabled = True
btnLoadStop.Visible = True
MemoEdit2.Text = ""
MemoEdit2.Text &= $"[{Now:HH:mm:ss}] Baue {LOAD_CLIENT_COUNT} Verbindungen auf…" & Environment.NewLine
' ► IProgress für thread-sicheres UI-Feedback während des Aufbaus
Dim progress As New Progress(Of String)(Sub(msg)
MemoEdit2.Text &= msg & Environment.NewLine
MemoEdit2.SelectionStart = MemoEdit2.Text.Length
MemoEdit2.ScrollToCaret()
End Sub)
Dim token = _loadCts.Token
Task.Run(
Async Function()
' ► ConcurrentBag ersetzt List(Of Client) + SyncLock beim Hinzufügen
Dim bag As New System.Collections.Concurrent.ConcurrentBag(Of Client)
Dim errors As Integer = 0
Dim options As New ParallelOptions With {
.CancellationToken = token,
.MaxDegreeOfParallelism = Math.Min(LOAD_CLIENT_COUNT, 32) ' ◄ Threadpool schonen
}
Try
Parallel.For(0, LOAD_CLIENT_COUNT, options,
Sub(i)
token.ThrowIfCancellationRequested()
Try
Dim oClientName = $"PerformanceClient_{i + 1}"
Dim oClient = New Client(LOGCONFIG, CONFIG.Config.EDMIAppServer, oClientName)
Dim oConnected = oClient.Connect()
If oConnected Then
bag.Add(oClient)
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Client #{i + 1} verbunden")
Else
oClient.Dispose()
Interlocked.Increment(errors)
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Client #{i + 1} Connect() fehlgeschlagen")
End If
Catch ex As OperationCanceledException
' Abbruch gewünscht – still beenden
Catch ex As Exception
Interlocked.Increment(errors)
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Client #{i + 1} Fehler: {ex.Message}")
End Try
End Sub)
Catch ex As OperationCanceledException
' ◄ Parallel.For selbst kann werfen – abfangen
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Verbindungsaufbau abgebrochen.")
End Try
' ► Erst nach Abschluss: thread-sicher in _loadClients übertragen
Dim connectedClients = bag.ToList()
Me.Invoke(Sub()
' ◄ Zuweisung auf UI-Thread – keine Race Condition
_loadClients = connectedClients
FlushLoadLog()
MemoEdit2.Text &= $"[{Now:HH:mm:ss}] {_loadClients.Count} Clients aktiv, {errors} Fehler" & Environment.NewLine
If _loadClients.Count > 0 Then
StartLoadTimer()
Else
' ◄ Kein einziger Client verbunden → Test nicht starten
MemoEdit2.Text &= $"[{Now:HH:mm:ss}] Kein Client verbunden – Lasttest wird nicht gestartet." & Environment.NewLine
btnLoadStart.Enabled = True
btnLoadStop.Enabled = False
End If
End Sub)
End Function)
End Sub
Private Sub StopLoadTest()
_loadCts?.Cancel()
If _loadTimer IsNot Nothing Then
_loadTimer.Stop()
_loadTimer.Dispose()
_loadTimer = Nothing
End If
' Alle Client-Verbindungen sauber schließen
If _loadClients IsNot Nothing Then
For Each oClient In _loadClients
Try
oClient.Dispose()
Catch
End Try
Next
_loadClients.Clear()
End If
MemoEdit2.Text &= $"[{Now:HH:mm:ss}] Lasttest beendet." & Environment.NewLine
btnLoadStart.Enabled = True
btnLoadStop.Enabled = False
End Sub
' ── Timer für zyklische Lastoperationen ───────────────────────────────
Private Sub StartLoadTimer()
_loadTimer = New System.Windows.Forms.Timer With {.Interval = 1000} ' jede Sekunde
AddHandler _loadTimer.Tick, AddressOf LoadTimer_Tick
_loadTimer.Start()
End Sub
Private Sub LoadTimer_Tick(sender As Object, e As EventArgs)
_timerTick += 1
FlushLoadLog()
Task.Run(Sub()
If _loadClients Is Nothing OrElse _loadClients.Count = 0 Then Return
Dim oSnapshot As List(Of Client)
SyncLock _loadClients
oSnapshot = _loadClients.ToList()
End SyncLock
' ← MaxDegreeOfParallelism begrenzt gleichzeitige Service-Calls
Parallel.ForEach(oSnapshot,
New ParallelOptions With {
.CancellationToken = _loadCts.Token,
.MaxDegreeOfParallelism = 10 ' ← NEU: max. 10 gleichzeitige Calls
},
Sub(oClient As Client)
Try
Select Case _timerTick Mod 4
Case 0
' ► GetStatus je Client
Dim st = oClient.GetStatus()
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Status: Online={st.IsOnline}, Conn={st.ActiveConnections}")
Case 1
' ► Zufällige Tabelle aus dem Cache des jeweiligen Clients laden
Dim oCachedTables = oClient.GetStatus().CachedTables
If oCachedTables IsNot Nothing AndAlso oCachedTables.Count > 0 Then
Dim sTable As String
SyncLock _rnd
sTable = oCachedTables(_rnd.Next(0, oCachedTables.Count))
End SyncLock
Dim oResult = oClient.GetDatatableByName(sTable)
_loadLog.Enqueue($"[{Now:HH:mm:ss}] GetDatatableByName → {sTable} OK={oResult?.OK} Rows={oResult?.Table?.Rows.Count}")
Else
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Cache leer – kein Tabellen-Zugriff möglich")
End If
Case 2
' ► Cache-Zugriff
Dim st = oClient.GetStatus()
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Cached Tables: {st.CachedTableCount}")
Case 3
' ► Channel-State prüfen
Dim st = oClient.GetStatus()
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Channel: {st.ChannelState}")
End Select
Catch ex As OperationCanceledException
' Lasttest gestoppt
Catch ex As Exception
_loadLog.Enqueue($"[{Now:HH:mm:ss}] Fehler: {ex.Message}")
End Try
End Sub)
Me.Invoke(Sub() FlushLoadLog())
End Sub)
End Sub
' ── Log-Einträge in ListBox schreiben ─────────────────────────────────
Private Sub FlushLoadLog()
Dim msg As String = Nothing
Dim added As Boolean = False
Dim sb As New System.Text.StringBuilder()
While _loadLog.TryDequeue(msg)
sb.AppendLine(msg)
added = True
End While
If added Then
MemoEdit2.Text &= sb.ToString()
MemoEdit2.SelectionStart = MemoEdit2.Text.Length
MemoEdit2.ScrollToCaret()
End If
End Sub
' ── Aufräumen beim Schließen ──────────────────────────────────────────
Private Sub frmAdmin_AppserviceTest_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
StopLoadTest()
End Sub
Private Sub btnLoadStop_Click(sender As Object, e As EventArgs) Handles btnLoadStop.Click
StopLoadTest()
End Sub
Private Sub frmAdmin_AppserviceTest_Load(sender As Object, e As EventArgs) Handles Me.Load
End Sub
End Class