diff --git a/ILSpy.Tests/Views/DebugStepsTests.cs b/ILSpy.Tests/Views/DebugStepsTests.cs index 4c26dadb0..1413e30ac 100644 --- a/ILSpy.Tests/Views/DebugStepsTests.cs +++ b/ILSpy.Tests/Views/DebugStepsTests.cs @@ -172,15 +172,13 @@ public class DebugStepsTests var replayStep = transformGroupWithChanges.Children.First(); var tab = vm.DockWorkspace.ActiveDecompilerTab!; - tab.RestartDecompileWithStepLimit(replayStep.BeginStep, isDebug: false, replayStep.BeginStep); - tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); + await tab.RestartDecompileWithStepLimit(replayStep.BeginStep, isDebug: false, replayStep.BeginStep); tab.Text.Should().NotBeNullOrWhiteSpace("C# replay before a selected AST mutation step must still emit code"); tab.DebugStepHighlight.Should().NotBeNull("C# replay before a selected AST mutation step must locate the changed node"); debugStepsVm.Steps.Should().BeSameAs(collectedSteps, "a step-limited C# replay must not replace the full step tree shown by the pane"); - tab.RestartDecompileWithStepLimit(replayStep.EndStep, isDebug: false, replayStep.BeginStep); - tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); + await tab.RestartDecompileWithStepLimit(replayStep.EndStep, isDebug: false, replayStep.BeginStep); tab.Text.Should().NotBeNullOrWhiteSpace("C# replay after a selected AST mutation step must still emit code"); tab.DebugStepHighlight.Should().NotBeNull("C# replay after a selected AST mutation step must locate the changed node"); debugStepsVm.Steps.Should().BeSameAs(collectedSteps, @@ -226,15 +224,13 @@ public class DebugStepsTests var collectedSteps = debugStepsVm.Steps; var tab = vm.DockWorkspace.ActiveDecompilerTab!; - tab.RestartDecompileWithStepLimit(replayStep!.BeginStep, isDebug: false, replayStep.BeginStep); - tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); + await tab.RestartDecompileWithStepLimit(replayStep!.BeginStep, isDebug: false, replayStep.BeginStep); tab.Text.Should().NotBeNullOrWhiteSpace("ILAst replay before a selected step must still emit IL"); tab.DebugStepHighlight.Should().NotBeNull("ILAst replay before a selected step must locate the changed instruction"); debugStepsVm.Steps.Should().BeSameAs(collectedSteps, "a step-limited ILAst replay must not replace the full step tree shown by the pane"); - tab.RestartDecompileWithStepLimit(replayStep.EndStep, isDebug: false, replayStep.BeginStep); - tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); + await tab.RestartDecompileWithStepLimit(replayStep.EndStep, isDebug: false, replayStep.BeginStep); tab.Text.Should().NotBeNullOrWhiteSpace("ILAst replay after a selected step must still emit IL"); tab.DebugStepHighlight.Should().NotBeNull("ILAst replay after a selected step must locate the changed instruction"); diff --git a/ILSpy/TextView/DecompilerTabPageModel.cs b/ILSpy/TextView/DecompilerTabPageModel.cs index 4091e3012..34376996e 100644 --- a/ILSpy/TextView/DecompilerTabPageModel.cs +++ b/ILSpy/TextView/DecompilerTabPageModel.cs @@ -401,12 +401,12 @@ namespace ICSharpCode.ILSpy.TextView /// is ). toggles the transforms' /// verbose-debug emission. No-op when there's nothing currently being decompiled. /// - public void RestartDecompileWithStepLimit(int stepLimit, bool isDebug, int? highlightStep = null) + public Task RestartDecompileWithStepLimit(int stepLimit, bool isDebug, int? highlightStep = null) { pendingStepLimit = stepLimit; pendingHighlightStep = highlightStep; pendingIsDebug = isDebug; - StartDecompile(); + return StartDecompile(); } /// Re-runs the current decompile with a larger output-length limit (the "Display code @@ -463,16 +463,20 @@ namespace ICSharpCode.ILSpy.TextView ICSharpCode.ILSpy.Commands.SaveCodeHelper.SaveNodeAsync(node, languageService, dockWorkspace).HandleExceptions(); } - // Fire-and-forget wrapper around DecompileAsync that observes the resulting Task. - // Without this, exceptions raised by the dispatched property setters (e.g. the - // PropertyChanged subscribers in DecompilerTextView) become UnobservedTaskException - // faults that the finalizer thread rethrows much later, hiding the originating bug. - void StartDecompile() + // Starts a decompile and returns the Task that completes once the output has been applied, + // so callers (e.g. the Debug Steps replay, and tests) can await completion deterministically + // instead of polling IsDecompiling/Text. A fault-observing continuation is attached so that + // exceptions raised by the dispatched property setters (e.g. the PropertyChanged subscribers + // in DecompilerTextView) don't become UnobservedTaskException faults the finalizer thread + // rethrows much later, hiding the originating bug. + Task StartDecompile() { - pendingDecompile = DecompileAsync().ContinueWith(t => { + var decompile = DecompileAsync(); + pendingDecompile = decompile.ContinueWith(t => { if (t.Exception is { } ex) System.Diagnostics.Debug.WriteLine($"DecompileAsync faulted: {ex.Flatten()}"); }, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously); + return decompile; } ///