From cb5bc9ac00ebcb50ad8ae2edd642dbafa8fa698a Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:14:47 -0400 Subject: [PATCH] Fix Dashboard auto-refresh: async loop, survive tab close, prevent legend duplication Replace DispatcherTimer with async Task.Delay loop to prevent priority starvation under heavy UI load. Don't cancel the loop on Unloaded (WPF fires Unloaded on tab switch/close, not just control destruction). Catch OperationCanceledException from SQL queries without killing the loop. Skip auto-refresh ticks while a full refresh is in progress to prevent concurrent chart rendering that duplicates legends. Co-Authored-By: Claude Opus 4.6 (1M context) --- Dashboard/ServerTab.xaml.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Dashboard/ServerTab.xaml.cs b/Dashboard/ServerTab.xaml.cs index a67daf8..4f990bf 100644 --- a/Dashboard/ServerTab.xaml.cs +++ b/Dashboard/ServerTab.xaml.cs @@ -375,6 +375,7 @@ private async void StartAutoRefreshLoop(int intervalSeconds) { await Task.Delay(TimeSpan.FromSeconds(intervalSeconds), cts.Token); if (cts.Token.IsCancellationRequested) break; + if (_isRefreshing) continue; try { @@ -384,6 +385,11 @@ private async void StartAutoRefreshLoop(int intervalSeconds) FooterText.Text = $"Last refresh: {DateTime.Now:yyyy-MM-dd HH:mm:ss} | Server: {_serverConnection.DisplayName}"; Logger.Info($"Auto-refresh completed in {sw.ElapsedMilliseconds}ms for {_serverConnection.DisplayName}"); } + catch (OperationCanceledException) when (!cts.Token.IsCancellationRequested) + { + // SQL query cancelled or timed out, but our loop CTS is still alive — keep going + Logger.Error($"Auto-refresh query cancelled for {_serverConnection.DisplayName}, continuing loop"); + } catch (Exception ex) when (ex is not OperationCanceledException) { Logger.Error($"Auto-refresh error: {ex.Message}", ex); @@ -393,13 +399,15 @@ private async void StartAutoRefreshLoop(int intervalSeconds) } catch (OperationCanceledException) { - // Normal shutdown + Logger.Info($"Auto-refresh loop stopped for {_serverConnection.DisplayName}"); } } private void ServerTab_Unloaded(object sender, RoutedEventArgs e) { - _autoRefreshCts?.Cancel(); + // Don't cancel auto-refresh on tab switch — WPF fires Unloaded when + // a TabItem is deselected, not just when the control is destroyed. + // The loop is lightweight and should keep ticking in the background. _autoRefreshTimer?.Stop(); _autoRefreshTimer = null;