Working on a prototype this week I needed a console app to create and start a periodic background processing activity, then sit waiting while the background processing activity executed every X seconds, untill you pressed the Ctrl+C key, which stopped the background processing and terminated the application.
Ok, so I needed;
- A background thread to execute over some activity
- A waitable event which could be signalled by an external entity to indicate that processing should stop.
- A waitable event which signalled that the processing activity had stopped.
- Some simulated wait state condition (i.e. wait for user keypress)
I created a class to implement the processing activity and also to control the processing service. The class contains 2 AutoResetEvents, 1 is used to signal the processing service to stop (the Cancel event), the other is used to indicate that the processing service has stopped (Stopped event).
The service is controlled by performing a timed wait on the Cancel event, if the wait times out, the the processing activity is executed, after which we re-enter the timed wait. If the wait doesn’t time out, this indicates that the Cancel event has been set, in which case the processing service is cleaned up and finishes, by setting the Stopped event.
This is much easier to see in code;
internal class ServiceStartupInfo { public int ProcessActivityIntervalMs { get; set; } } internal class InterruptableBackgroundProcess { private readonly int _processIntervalMs = 30000; private readonly AutoResetEvent _canYouStopPlease = new AutoResetEvent(false); private readonly AutoResetEvent _heyEveryoneIveFinished = new AutoResetEvent(false); private InterruptableBackgroundProcess(ServiceStartupInfo startInfo) { _processIntervalMs = Math.Min(Math.Max(10000, startInfo.ProcessActivityIntervalMs), 600000); } public AutoResetEvent SignalProcessToStop { get { return _canYouStopPlease; } } public AutoResetEvent ProcessHasCompleted { get { return _heyEveryoneIveFinished; } } private void ExecuteActivity() { Console.WriteLine("Begin Background Processing...."); Thread.Sleep(5000); Console.WriteLine("Finished Background Processing...."); } private void ProcessController() { // wait on the threads SignalProcessToStop event var wh = new WaitHandle[] { SignalProcessToStop }; while (true) { Console.WriteLine("Wait for Background Processing"); Console.WriteLine("Press CTRL+C to exit...."); var fTimedOut = !WaitHandle.WaitAll(wh, _processIntervalMs); if (fTimedOut) ExecuteActivity(); // if we timed out, do the processing else break; // thread process was signalled to stop } // indicate the processing service has stopped ProcessHasCompleted.Set(); } /// <summary> /// Initialises the processing controller, process events and starts the process. /// </summary> /// <returns></returns> public static WaitHandle[] InitialiseServiceProcess(ServiceStartupInfo startInfo) { var processSvc = new InterruptableBackgroundProcess(startInfo); var thread = new Thread(processSvc.ProcessController); thread.Start(); return new[] { processSvc.SignalProcessToStop, processSvc.ProcessHasCompleted }; } }
So thats the background processing service, what now for the console apps wait state, waiting for the Ctrl+C keypress. Again this was pretty simple, the main application thread waits on a Console.ReadKey() call, when you press Ctrl+C the main application thread then Sets the background processing service’s Cancel event and then waits for the background processing service’s Stopped event to become signalled, as shown below;
class Program { private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) { e.Cancel = false; /* don't terminate the app */ } static void Main(string[] args) { Console.TreatControlCAsInput = true; Console.CancelKeyPress += Console_CancelKeyPress; Console.Title = "Interruptable Background Processing"; // setup service startup defaults var startInfo = new ServiceStartupInfo { ProcessActivityIntervalMs = 10000 }; // initialise the background processing service var serviceControlEvents = InterruptableBackgroundProcess.InitialiseServiceProcess(startInfo); // now simulate a waitstate by waiting until the user presses ctrl+c while (true) { var ci = Console.ReadKey(true); if ((ci.Modifiers & ConsoleModifiers.Control) != 0 && ci.Key == ConsoleKey.C) break; // CTRL+C pressed } // signal the event to tell the service to stop, then wait for the event // to be set which indicates the service has stopped WaitHandle.SignalAndWait(serviceControlEvents[0], serviceControlEvents[1]); Console.Write(Environment.NewLine + "Finished, press any key..."); Console.ReadKey(); } }
Published by