From 50c402098f8b4061f95f3008f273cafa7fa7db5f Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 1 Jul 2026 18:32:01 +0200 Subject: [PATCH] Clear debug steps synchronously on selection to avoid a race OnSelectionChanged deferred its Steps = null via Dispatcher.Post. The selection message is raised synchronously on the UI thread right as the new selection's decompile is kicked off (AssemblyTreeModel.RaiseSelectionChanged), so a deferred clear can land on a later dispatcher cycle than the decompile's StepperUpdated populate post and wipe the freshly recorded steps -- leaving the pane empty until the next decompile (the intermittent CI timeout in the debug-step UI tests, where both C# and ILAst share this handler). Clear synchronously so the blank is pinned to the selection moment, strictly before that selection's decompile can finish and post its steps. Assisted-by: Claude:claude-opus-4-8:Claude Code --- ILSpy/ViewModels/DebugStepsPaneModel.cs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/ILSpy/ViewModels/DebugStepsPaneModel.cs b/ILSpy/ViewModels/DebugStepsPaneModel.cs index e26460608..1eb7abeb4 100644 --- a/ILSpy/ViewModels/DebugStepsPaneModel.cs +++ b/ILSpy/ViewModels/DebugStepsPaneModel.cs @@ -220,12 +220,26 @@ namespace ICSharpCode.ILSpy.ViewModels void OnSelectionChanged(object? sender, AssemblyTreeSelectionChangedEventArgs e) { - // User picked a new tree node — the previous run's stepper is stale until the - // next decompile populates it. - Dispatcher.UIThread.Post(() => { + // User picked a new tree node — the previous run's stepper is stale until the next + // decompile populates it. Clear synchronously (this message is raised on the UI thread, + // right as the new selection's decompile is kicked off) so the blanking is pinned before + // that decompile can finish and post its StepperUpdated. A deferred clear could otherwise + // float to a dispatcher cycle after the populate and wipe the fresh steps, leaving the + // pane empty until the next decompile — the intermittent "no steps" race. + if (Dispatcher.UIThread.CheckAccess()) + { + ClearSteps(); + } + else + { + Dispatcher.UIThread.Post(ClearSteps); + } + + void ClearSteps() + { Steps = null; lastSelectedStep = int.MaxValue; - }); + } } void OnWritingOptionsChanged(object? sender, PropertyChangedEventArgs e)