Created
April 6, 2016 18:18
-
-
Save eric-b/0db6e8a65775de81799d56dcdd46f9ba to your computer and use it in GitHub Desktop.
SetConsoleCtrlHandler
This file contains 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
using System.Runtime.InteropServices; | |
namespace ConsoleDemo | |
{ | |
internal static class NativeMethods | |
{ | |
// Doc MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686016(v=vs.85).aspx | |
/// <summary> | |
/// <para>Adds or removes an <see cref="HandlerRoutine"/> delegate function | |
/// from the list of handler functions for the calling process.</para> | |
/// <para>If no delegate function is specified, the function sets an inheritable | |
/// attribute that determines whether the calling process ignores CTRL+C signals.</para> | |
/// </summary> | |
/// <param name="handler"></param> | |
/// <param name="add"><para>If this parameter is TRUE, the handler is added; if it is FALSE, the handler is removed.</para> | |
/// <remarks>If the HandlerRoutine parameter is NULL, a TRUE value causes the calling process to ignore CTRL+C input, | |
/// and a FALSE value restores normal processing of CTRL+C input. | |
/// This attribute of ignoring or processing CTRL+C is inherited by child processes.</remarks></param> | |
/// <returns><c>true</c> if the function succeeds.</returns> | |
[DllImport("Kernel32")] | |
public static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add); | |
/// <summary> | |
/// <para>A delegate type to be used as the handler routine | |
/// for <see cref="SetConsoleCtrlHandler"/>. </para> | |
/// <para>A console process uses this function to handle control signals received by the process. | |
/// When the signal is received, the system creates a new thread in the process to execute the function.</para> | |
/// </summary> | |
/// <param name="ctrlType"></param> | |
/// <returns>If the function handles the control signal, it should return TRUE. | |
/// If it returns FALSE, the next handler function in the list of handlers for this process is used. | |
/// See MSDN remarks for details.</returns> | |
public delegate bool HandlerRoutine(CtrlTypes ctrlType); | |
/// <summary> | |
/// The type of control signal received by <see cref="HandlerRoutine"/>. | |
/// </summary> | |
public enum CtrlTypes | |
{ | |
/// <summary> | |
/// A CTRL+C signal was received, either from keyboard input or from a signal generated by | |
/// the GenerateConsoleCtrlEvent function. | |
/// </summary> | |
CTRL_C_EVENT = 0, | |
/// <summary> | |
/// A CTRL+BREAK signal was received, either from keyboard input or from a signal | |
/// generated by GenerateConsoleCtrlEvent. | |
/// </summary> | |
CTRL_BREAK_EVENT = 1, | |
/// <summary> | |
/// A signal that the system sends to all processes attached to a console when the user | |
/// closes the console (either by clicking Close on the console window's window menu, | |
/// or by clicking the End Task button command from Task Manager). | |
/// </summary> | |
CTRL_CLOSE_EVENT = 2, | |
/// <summary> | |
/// <para>A signal that the system sends to all console processes when a user is logging off. | |
/// This signal does not indicate which user is logging off, so no assumptions can be made.</para> | |
/// <remarks>Note that this signal is received only by services. | |
/// Interactive applications are terminated at logoff, | |
/// so they are not present when the system sends this signal.</remarks> | |
/// </summary> | |
CTRL_LOGOFF_EVENT = 5, | |
/// <summary> | |
/// <para>A signal that the system sends when the system is shutting down. | |
/// Interactive applications are not present by the time the system sends this signal, | |
/// therefore it can be received only be services in this situation. | |
/// Services also have their own notification mechanism for shutdown events. | |
/// For more information, see Handler.</para> | |
/// <remarks>This signal can also be generated by an application using GenerateConsoleCtrlEvent.</remarks> | |
/// </summary> | |
CTRL_SHUTDOWN_EVENT = 6 | |
} | |
} | |
} |
This file contains 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
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Text; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace ConsoleDemo | |
{ | |
sealed class Program : IDisposable | |
{ | |
private static Program _program; | |
private static bool _isClosing; | |
private readonly CancellationTokenSource _cts; | |
private readonly ManualResetEvent _signal; | |
private int _disposeCount; | |
public Program() | |
{ | |
_cts = new CancellationTokenSource(); | |
_signal = new ManualResetEvent(initialState: false); | |
} | |
public void StartTaskAndBlock() | |
{ | |
Task task = Task.Delay(20000, _cts.Token); | |
WriteLine("Busy..."); | |
try | |
{ | |
task.Wait(); | |
} | |
catch (AggregateException ex) | |
{ | |
WriteLine(ex.Flatten().ToString()); | |
} | |
finally | |
{ | |
// simulate exit latency for a real program | |
WriteLine("Stopping..."); | |
Thread.Sleep(3000); | |
_signal.Set(); | |
} | |
} | |
public void CancelAndBlock() | |
{ | |
try | |
{ | |
_cts.Cancel(); | |
_signal.WaitOne(); | |
((IDisposable)this).Dispose(); | |
} | |
catch (Exception ex) | |
{ | |
WriteLine(ex.ToString()); | |
} | |
} | |
static void Main(string[] args) | |
{ | |
try | |
{ | |
using (_program = new Program()) | |
{ | |
NativeMethods.SetConsoleCtrlHandler(ConsoleCtrlHandler, add: true); | |
_program.StartTaskAndBlock(); | |
} | |
} | |
catch(Exception ex) | |
{ | |
WriteLine(ex.ToString()); | |
} | |
} | |
static bool ConsoleCtrlHandler(NativeMethods.CtrlTypes ctrlType) | |
{ | |
if (_isClosing) | |
return false; | |
_isClosing = true; | |
const bool signalHandled = true, signalIgnored = false; | |
if (ctrlType == NativeMethods.CtrlTypes.CTRL_C_EVENT || | |
ctrlType == NativeMethods.CtrlTypes.CTRL_CLOSE_EVENT) | |
{ | |
try | |
{ | |
_program.CancelAndBlock(); | |
return signalHandled; | |
} | |
catch (Exception ex) | |
{ | |
WriteLine(ex.ToString()); | |
throw; | |
} | |
} | |
return signalIgnored; | |
} | |
void IDisposable.Dispose() | |
{ | |
if (Interlocked.Increment(ref _disposeCount) != 1) | |
return; | |
_cts.Dispose(); | |
_signal.Dispose(); | |
WriteLine("Clean exit!"); | |
} | |
static void WriteLine(string format, params object[] args) | |
{ | |
string line = string.Format(format, args); | |
Console.WriteLine(line); | |
Debug.WriteLine(line); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment