[dotnet run/watch] gracefully handle Ctrl+C for Windows (WinForms, WPF, MAUI)#53127
Merged
tmat merged 8 commits intorelease/10.0.3xxfrom Mar 6, 2026
Merged
[dotnet run/watch] gracefully handle Ctrl+C for Windows (WinForms, WPF, MAUI)#53127tmat merged 8 commits intorelease/10.0.3xxfrom
tmat merged 8 commits intorelease/10.0.3xxfrom
Conversation
…F, MAUI) Fixes: #52473 There are really two concerns here: `dotnet run` and `dotnet watch`. ~~ dotnet run ~~ For `dotnet run`, we want to ensure that Ctrl+C is handled gracefully for Windows applications (WinForms, WPF, MAUI) that do not handle Ctrl+C. In these cases, the UI framework disables the console. As a result, when a user tries to stop the application using Ctrl+C: nothing happens! So, when `OutputType` is `WinExe`, we will use `Process.CloseMainWindow()` to close the application instead of sending a Ctrl+C signal. This should work for nearly all Windows applications, unless there is a modal dialog preventing `CloseMainWindow()` from working. ~~ dotnet watch ~~ For `dotnet watch`, the behavior is similar, but with the issue that `dotnet watch` launches `dotnet run` as a "process group". When doing this, default Ctrl+C handling is lost. To restore Ctrl+C handling from inside `dotnet run`, we can call: SetConsoleCtrlHandler(null, false); [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetConsoleCtrlHandler(Delegate? handler, bool add); When running *inside* a process group, this restores the default Ctrl+C handling, and is a no-op otherwise. The only reason this works for console apps, is the `$DOTNET_STARTUP_HOOKS` assembly does this work instead, allowing `dotnet watch` to signal Ctrl+C to the grandchild process instead of `dotnet run`. Since Windows applications (WinForms, WPF, MAUI) ignore Ctrl+C signals, the grandchild process does nothing. https://github.com/dotnet/sdk/blob/f284d9f49aec157d49233ab5785cbcf78ed1ea58/src/BuiltInTools/HotReloadAgent.Host/StartupHook.cs#L104-L116 I don't think we should remove the `SetConsoleCtrlHandler()` call from the startup hook, as it allows Ctrl+C to work for console applications.
Contributor
There was a problem hiding this comment.
Pull request overview
Improves Ctrl+C handling for Windows GUI apps (WinForms/WPF/MAUI) launched via dotnet run and dotnet watch, where Ctrl+C may otherwise be ignored due to console behavior/process-group semantics.
Changes:
- Plumbs MSBuild
OutputTypeintodotnet runso WinExe apps can be terminated viaProcess.CloseMainWindow()on Ctrl+C. - Re-enables Windows Ctrl+C handling in
dotnet runwhen launched in a new process group (e.g., bydotnet watch). - Adds a WinForms WinExe test asset and new tests covering graceful termination paths.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| test/dotnet.Tests/CommandTests/Run/GivenDotnetRunIsInterrupted.cs | Adds a Windows-only dotnet run interruption test for WinExe apps. |
| test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs | Adds a Windows watch test validating graceful termination for WinExe apps. |
| test/TestAssets/TestProjects/WatchWinExeApp/WatchWinExeApp.csproj | New WinExe WinForms test project used by run/watch tests. |
| test/TestAssets/TestProjects/WatchWinExeApp/Program.cs | New WinForms app that reports whether it closed “gracefully”. |
| src/Cli/dotnet/Commands/Run/RunProperties.cs | Adds OutputType to run properties retrieved from MSBuild. |
| src/Cli/dotnet/Commands/Run/RunCommand.cs | Enables CloseMainWindow termination behavior for OutputType=WinExe. |
| src/Cli/Microsoft.DotNet.Cli.Utils/ProcessReaper.cs | Re-enables Windows Ctrl+C handling and adds CloseMainWindow-on-Ctrl+C option. |
| src/Cli/Microsoft.DotNet.Cli.Utils/Command.cs | Passes CloseMainWindow option into ProcessReaper. |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
tmat
reviewed
Feb 24, 2026
tmat
reviewed
Feb 24, 2026
test/dotnet.Tests/CommandTests/Run/GivenDotnetRunIsInterrupted.cs
Outdated
Show resolved
Hide resolved
tmat
reviewed
Feb 24, 2026
…pers/ctrl-c-windows-apps # Conflicts: # test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs
tmat
approved these changes
Mar 6, 2026
This was referenced Mar 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes: #52473
There are really two concerns here:
dotnet runanddotnet watch.dotnet run
For
dotnet run, we want to ensure that Ctrl+C is handled gracefully for Windows applications (WinForms, WPF, MAUI) that do not handle Ctrl+C. In these cases, the UI framework disables the console. As a result, when a user tries to stop the application using Ctrl+C: nothing happens!So, when
OutputTypeisWinExe, we will useProcess.CloseMainWindow()to close the application instead of sending a Ctrl+C signal. This should work for nearly all Windows applications, unless there is a modal dialog preventingCloseMainWindow()from working.dotnet watch
For
dotnet watch, the behavior is similar, but with the issue thatdotnet watchlaunchesdotnet runas a "process group". When doing this, default Ctrl+C handling is lost.To restore Ctrl+C handling from inside
dotnet run, we can call:When running inside a process group, this restores the default Ctrl+C handling, and is a no-op otherwise.
The only reason this works for console apps, is the
$DOTNET_STARTUP_HOOKSassembly does this work instead, allowingdotnet watchto signal Ctrl+C to the grandchild process instead ofdotnet run. Since Windows applications (WinForms, WPF, MAUI) ignore Ctrl+C signals, the grandchild process does nothing.sdk/src/BuiltInTools/HotReloadAgent.Host/StartupHook.cs
Lines 104 to 116 in f284d9f
I don't think we should remove the
SetConsoleCtrlHandler()call from the startup hook, as it allows Ctrl+C to work for console applications.