C#
console application
exit event
application termination
event handling

Capture console exit C

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Capturing console application exit in C# allows you to perform cleanup (closing files, flushing logs, releasing resources) before the process terminates. The primary mechanism is Console.CancelKeyPress for Ctrl+C handling. For other exit scenarios (window close, system shutdown), use the Win32 SetConsoleCtrlHandler API via P/Invoke. AppDomain.CurrentDomain.ProcessExit fires on normal exit but has a strict time limit.

Console.CancelKeyPress (Ctrl+C / Ctrl+Break)

csharp
1using System;
2
3class Program
4{
5    static void Main()
6    {
7        Console.CancelKeyPress += (sender, args) =>
8        {
9            Console.WriteLine("Ctrl+C detected. Cleaning up...");
10
11            // Prevent immediate termination
12            args.Cancel = true;
13
14            // Perform cleanup
15            CleanUp();
16        };
17
18        Console.WriteLine("Running. Press Ctrl+C to exit.");
19        while (true)
20        {
21            // Main application loop
22            System.Threading.Thread.Sleep(1000);
23            Console.Write(".");
24        }
25    }
26
27    static void CleanUp()
28    {
29        Console.WriteLine("\nSaving state...");
30        Console.WriteLine("Closing connections...");
31        Environment.Exit(0);
32    }
33}

Setting args.Cancel = true prevents the default behavior (immediate termination) and lets your code run cleanup logic before calling Environment.Exit().

AppDomain.ProcessExit (Normal Exit)

csharp
1using System;
2
3class Program
4{
5    static void Main()
6    {
7        AppDomain.CurrentDomain.ProcessExit += (sender, args) =>
8        {
9            Console.WriteLine("Process is exiting. Running cleanup...");
10            // Flush logs, close files, dispose resources
11            // WARNING: This handler has roughly a 2 second time limit
12        };
13
14        Console.WriteLine("Application running...");
15        // Application does work...
16
17        Console.WriteLine("Exiting normally.");
18        // ProcessExit fires after Main() returns
19    }
20}

ProcessExit fires when the application exits normally (end of Main, Environment.Exit). It does NOT fire on forced kills (taskkill /F, kill -9).

SetConsoleCtrlHandler (All Exit Types)

For comprehensive exit detection including window close and system shutdown, use the Win32 API:

csharp
1using System;
2using System.Runtime.InteropServices;
3
4class Program
5{
6    // Win32 API import
7    [DllImport("Kernel32")]
8    private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add);
9
10    private delegate bool ConsoleCtrlDelegate(CtrlTypes type);
11
12    enum CtrlTypes
13    {
14        CTRL_C_EVENT = 0,
15        CTRL_BREAK_EVENT = 1,
16        CTRL_CLOSE_EVENT = 2,
17        CTRL_LOGOFF_EVENT = 5,
18        CTRL_SHUTDOWN_EVENT = 6
19    }
20
21    private static bool ConsoleCtrlHandler(CtrlTypes type)
22    {
23        switch (type)
24        {
25            case CtrlTypes.CTRL_C_EVENT:
26                Console.WriteLine("Ctrl+C pressed");
27                break;
28            case CtrlTypes.CTRL_BREAK_EVENT:
29                Console.WriteLine("Ctrl+Break pressed");
30                break;
31            case CtrlTypes.CTRL_CLOSE_EVENT:
32                Console.WriteLine("Console window closing");
33                break;
34            case CtrlTypes.CTRL_LOGOFF_EVENT:
35                Console.WriteLine("User logging off");
36                break;
37            case CtrlTypes.CTRL_SHUTDOWN_EVENT:
38                Console.WriteLine("System shutting down");
39                break;
40        }
41
42        CleanUp();
43        return true;  // Signal handled
44    }
45
46    static void Main()
47    {
48        SetConsoleCtrlHandler(ConsoleCtrlHandler, true);
49
50        Console.WriteLine("Running. Try closing the window or pressing Ctrl+C.");
51        while (true)
52        {
53            System.Threading.Thread.Sleep(1000);
54        }
55    }
56
57    static void CleanUp()
58    {
59        Console.WriteLine("Performing cleanup...");
60        // Save state, flush buffers, close connections
61    }
62}

Using CancellationToken (Modern Approach)

csharp
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4
5class Program
6{
7    static async Task Main()
8    {
9        using var cts = new CancellationTokenSource();
10
11        Console.CancelKeyPress += (sender, args) =>
12        {
13            args.Cancel = true;
14            cts.Cancel();  // Signal cancellation
15        };
16
17        AppDomain.CurrentDomain.ProcessExit += (sender, args) =>
18        {
19            cts.Cancel();
20        };
21
22        try
23        {
24            await RunApplicationAsync(cts.Token);
25        }
26        catch (OperationCanceledException)
27        {
28            Console.WriteLine("Application cancelled. Cleaning up...");
29        }
30        finally
31        {
32            await CleanUpAsync();
33        }
34    }
35
36    static async Task RunApplicationAsync(CancellationToken token)
37    {
38        while (!token.IsCancellationRequested)
39        {
40            await Task.Delay(1000, token);
41            Console.Write(".");
42        }
43    }
44
45    static async Task CleanUpAsync()
46    {
47        Console.WriteLine("\nFlushing data...");
48        await Task.Delay(500);  // Simulate async cleanup
49        Console.WriteLine("Done.");
50    }
51}

This is the recommended pattern for modern .NET applications. CancellationToken propagates through async methods, allowing graceful shutdown of all background tasks.

.NET Generic Host (ASP.NET Core / Worker Services)

csharp
1using Microsoft.Extensions.Hosting;
2
3var host = Host.CreateDefaultBuilder(args)
4    .ConfigureServices(services =>
5    {
6        services.AddHostedService<MyWorker>();
7    })
8    .Build();
9
10await host.RunAsync();
11
12// MyWorker.cs
13public class MyWorker : BackgroundService
14{
15    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
16    {
17        while (!stoppingToken.IsCancellationRequested)
18        {
19            Console.WriteLine("Working...");
20            await Task.Delay(1000, stoppingToken);
21        }
22    }
23
24    public override async Task StopAsync(CancellationToken cancellationToken)
25    {
26        Console.WriteLine("Worker stopping. Cleaning up...");
27        await base.StopAsync(cancellationToken);
28    }
29}

The Generic Host handles Ctrl+C, SIGTERM, and process exit automatically, calling StopAsync on all hosted services.

Linux/macOS Signal Handling (.NET 6+)

csharp
1using System;
2using System.Runtime.InteropServices;
3
4// Handle SIGTERM (sent by Docker, systemd, Kubernetes)
5PosixSignalRegistration.Create(PosixSignal.SIGTERM, ctx =>
6{
7    Console.WriteLine("SIGTERM received");
8    ctx.Cancel = true;  // Prevent default termination
9    // Perform cleanup
10});
11
12// Handle SIGINT (Ctrl+C, alternative to Console.CancelKeyPress)
13PosixSignalRegistration.Create(PosixSignal.SIGINT, ctx =>
14{
15    Console.WriteLine("SIGINT received");
16    ctx.Cancel = true;
17});

Common Pitfalls

  • ProcessExit time limit: The ProcessExit handler has approximately 2 seconds to complete. Long cleanup operations get cut off. Move heavy cleanup to before Main returns.
  • args.Cancel = true only works for Ctrl+C: Setting Cancel in CancelKeyPress prevents termination for Ctrl+C, but CTRL_CLOSE_EVENT (window close) gives you only about 5 seconds regardless.
  • SetConsoleCtrlHandler is Windows-only: The P/Invoke approach does not work on Linux/macOS. Use PosixSignalRegistration (.NET 6+) for cross-platform signal handling.
  • Environment.Exit() skips finally blocks: Calling Environment.Exit(0) from a handler terminates immediately, skipping finally blocks in other threads. Use CancellationToken for cooperative shutdown.
  • SIGKILL / taskkill /F cannot be caught: kill -9 and taskkill /F terminate the process immediately with no opportunity for cleanup. Design your application to recover from unclean shutdowns.

Summary

  • Use Console.CancelKeyPress for Ctrl+C handling with args.Cancel = true to prevent immediate exit
  • Use AppDomain.CurrentDomain.ProcessExit for normal exit cleanup (2-second limit)
  • Use SetConsoleCtrlHandler (Windows) or PosixSignalRegistration (.NET 6+, cross-platform) for comprehensive signal handling
  • The modern pattern uses CancellationToken for cooperative async shutdown
  • .NET Generic Host handles exit signals automatically for hosted services
  • No mechanism can catch forced kills (kill -9, taskkill /F)

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions