The definitive code that prevents a c# console app

2019-05-27 00:07发布

Can we work together to come up with something that works for control-c, control-break, log off, window X button pressed, etc?

Here is what I have so far:

class Program
{  
    private static ConsoleEventHandlerDelegate consoleHandler;
    delegate bool ConsoleEventHandlerDelegate(CtrlTypes eventCode);

    static void Main(string[] args)
    {
        consoleHandler = new ConsoleEventHandlerDelegate(ConsoleCtrlCheck);
        SetConsoleCtrlHandler(consoleHandler, true);

        System.Diagnostics.Process.GetCurrentProcess().Exited 
           += delegate(object sender, EventArgs e) 
        {              
            GeneralManager.Stop();
        };

        Console.CancelKeyPress += delegate(object sender,
                                ConsoleCancelEventArgs e)
        {
            e.Cancel = false;
            GeneralManager.Stop();
        };

        GeneralManager.Start();
    }

    private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
    {
        switch (ctrlType)
        {                
            case CtrlTypes.CTRL_C_EVENT:

                Console.WriteLine("CTRL+C received!");
                GeneralManager.Stop();
                break;

            case CtrlTypes.CTRL_BREAK_EVENT:
                isclosing = true;
                Console.WriteLine("CTRL+BREAK received!");
                GeneralManager.Stop();
                break;

            case CtrlTypes.CTRL_CLOSE_EVENT:

                Console.WriteLine("Program being closed!");
                GeneralManager.Stop();
                break;

            case CtrlTypes.CTRL_LOGOFF_EVENT:
            case CtrlTypes.CTRL_SHUTDOWN_EVENT:

                Console.WriteLine("User is logging off!");
                GeneralManager.Stop();
                break;                           
        }
        return true;
    }

    #region unmanaged

            [DllImport("kernel32.dll")]
          static extern bool SetConsoleCtrlHandler(ConsoleEventHandlerDelegate
            handlerProc, bool add);

    public delegate bool HandlerRoutine(CtrlTypes CtrlType);

    public enum CtrlTypes
    {
        CTRL_C_EVENT = 0,
        CTRL_BREAK_EVENT,
        CTRL_CLOSE_EVENT,
        CTRL_LOGOFF_EVENT = 5,
        CTRL_SHUTDOWN_EVENT
    }

    #endregion
}

Two problems:

  1. In the Managed Control-Break handler, if we set e.Cancel = true it fails with an exception for .Net4. This is noted in the MSDN article with no work-around: http://msdn.microsoft.com/en-us/library/system.consolecanceleventargs.cancel.aspx

  2. I don't know how to cancel the close in the ConsoleCtrlCheck. I get a second or two to do some cleanup, but I'd rather cancel and make sure it all gets done properly.

UPDATE:

Thanks for the replies. Upvoted both. Will wait to see if anyone can come up with a reply that directly solves what I asked for, otherwise will accept one of the "use NT services" answers.

3条回答
ゆ 、 Hurt°
2楼-- · 2019-05-27 00:21

I need to wait for pending user requests to complete, disconnect them cleanly, run a few queries on the database to reflect the change(s) in state and so forth. It's a TCP server.

Then don't run it as a Console or any other kind of Client app.

Just run it as a Windows (NT) Service and the only events you'll have to worry about are Power loss and a stop signal.

Use a UPS and make sure you can close in a reasonable timespan.

查看更多
Bombasti
3楼-- · 2019-05-27 00:33

I spent couple hours looking at this and as I don't have time now to build a working code; as while it's probably short, getting it right would take a while. I'll just give you link to the various stuff that's needed to get this done:

http://pastebin.com/EzX3ezrf

Summarizing the lessons from the code in the paste:

  1. Need a message pump to handle some/all of WM_QUERYENDSESSION, WM_ENDSESSION, CTRL_SHUTDOWN_EVENT (in c# SystemEvents.SessionEnding may cover some/all of these)

  2. Easiest way to get a message pump is to make it a hidden form/window app, but I recall it's possible to build as a console app and add a message pump also. I didn't include that code in the paste though.

  3. "If an application must block a potential system shutdown, it can call the ShutdownBlockReasonCreate function"

  4. As AllocConsole is used to create the console, you need to use SetConsoleCtrlHandler and use ExitThread(1) in the handler. This is a "hack" that kills off the thread that would close the console otherwise. It's used in FarManager. see interf.cpp for example

  5. You need to also initialize and clean up the console when using AllocConsole.

  6. Pressing CTRL+C is reported to mess up the input. I'm not sure if FarManager is handling this scenario. There's some code in the CTRL_BREAK_EVENT handler in interf.cpp that I'm not sure what it does.

  7. FarManager also handles WM_POWERBROADCAST, probably to do with suspending

If all that isn't enough (should be), you can also add the console into another process and IPC your messages to it like shown here. Why does closing a console that was started with AllocConsole cause my whole application to exit? Can I change this behavior?

RMTool can be used to simulate logoff/shutdown messages for testing: http://download.microsoft.com/download/d/2/5/d2522ce4-a441-459d-8302-be8f3321823c/LogoToolsv1.0.msi

MSDN has some C# code also at microsoft.win32.systemevents.sessionending.aspx and microsoft.win32.systemevents.aspx (hidden form example)

The mischel.com/pubs/consoledotnet/consoledotnet.zip has a sample winTest project with AllocConsole being used and some of the events handled.

查看更多
走好不送
4楼-- · 2019-05-27 00:40

I have not tried to do this kind of thing with a console app, but you may do better with a Windows Forms (or WCF app). They will give you a FormClosing event which is cancellable. Alternately, use a Windows Service if you are writing a network service, it provides an interface to cleanly stop your application.

If you are really keen on a console app, perhaps a try {} finally {} clause around all your code or something more exotic like a critical finaliser may allow you to run clean up code. But this is really not the right tool for the job.

And there are cases which you cannot prevent you app being closed, eg: power failure, or Task Manager kill command (and if an app didn't close via the X, Task Manager is the first tool I'd reach for).

So, code your service application such that all client requests are logged to a transaction log (like SQL server does). If you are unexpectedly interrupted (by whatever circumstance) anything which has happened up until that point is in the log. When your service next starts, replay that log.

One of your things to log will be "I was shutdown cleanly at time T". If you restart and don't find that item at the end of your log, you know something went wrong, and you can take whatever action is required.

If you need to know what your service is doing, use one of the many logging frameworks to pipe events to a second app, which just displays activity.

查看更多
登录 后发表回答