I am working on a data acquisition application and I want to ensure that it exits gracefully. That is, it processes all the already collected data, flushes all the (file) buffers to "disk" (persistent memory) and might even uploads the data to the cloud.
So, I wrote (based on this answer) the code below to catch every close event. (It is just a test code.)
Problem: If I use the X in the top-right corner of the console, the program gets terminated after a short delay, even though the termination sequence is still running. (The handler does get called, and it does start to wait for the threads to join but then it gets killed after a while.) If I terminate with Crt+C or Ctr+Break it works as intended; The termination sequence finishes and exits the process.
Question: How can I make the OS wait for my application to terminate instead of killing it off after a short grace period?
#region Trap application termination
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);
private delegate bool EventHandler(CtrlType sig);
static EventHandler _handler;
enum CtrlType
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT = 1,
CTRL_CLOSE_EVENT = 2,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT = 6
}
private static bool Handler(CtrlType sig, List<Thread> threads, List<Task> tasks, CancellationTokenSource cancellationRequest)
{
//starts new foregeound thread, so the process doesn't terminate when all the cancelled threads end
Thread closer = new Thread(() => terminationSequence(threads, tasks, cancellationRequest));
closer.IsBackground = false;
closer.Start();
closer.Join(); //wait for the termination sequence to finish
return true; //just to be pretty; this never runs (obviously)
}
private static void terminationSequence(List<Thread> threads, List<Task> tasks, CancellationTokenSource cancellationRequest)
{
cancellationRequest.Cancel(); //sends cancellation requests to all threads and tasks
//wait for all the tasks to meet the cancellation request
foreach (Task task in tasks)
{
task.Wait();
}
//wait for all the treads to meet the cancellation request
foreach (Thread thread in threads)
{
thread.Join();
}
/*maybe do some additional work*/
//simulate work being done
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Console.WriteLine("Spinning");
while (stopwatch.Elapsed.Seconds < 30)
{
if (stopwatch.Elapsed.Seconds % 2 == 0)
{
Console.Clear();
Console.WriteLine("Elapsed Time: {0}m {1}s", stopwatch.Elapsed.Minutes, stopwatch.Elapsed.Seconds);
}
Thread.SpinWait(10000);
}
Environment.Exit(0); //exit the process
}
#endregion
static void Main(string[] args)
{
CancellationTokenSource cancellationRequest = new CancellationTokenSource(); //cancellation signal to all threads and tasks
List<Thread> threads = new List<Thread>(); //list of threads
//specifys termination handler
_handler += new EventHandler((type) => Handler(type, threads, new List<Task>(), cancellationRequest));
SetConsoleCtrlHandler(_handler, true);
//creating a new thread
Thread t = new Thread(() => logic(cancellationRequest.Token));
threads.Add(t);
t.Start();
}