How to catch exception and stop Topshelf service?

2020-02-26 06:17发布

问题:

I have a topshelf windows service where I want to do some checking (i.e. if an xml file exists) and if the check fails I need the windows service to stop.

So I tried doing the check in the Start() method and then raise an exception:

public void Start()
{
    if (!File.Exists(_xmlFile) throw new FileNotFoundException();
    // Do some work here if xml file exists.
}

However, the windows service stays around as a process after the exception which I then have to kill manually in the task manager.

Is there a way to not run the service if certain conditions (i.e. file not found) hold?

回答1:

I've "borrowed" the sample code for the functional setup of topshelf to demonstrate a point:

HostFactory.Run(x =>                                 //1
    {
        x.Service<TownCrier>(s =>                        //2
        {
           s.ConstructUsing(name=> new TownCrier());     //3
           s.WhenStarted(tc => tc.Start());              //4
           s.WhenStopped(tc => tc.Stop());               //5
        });
        x.RunAsLocalSystem();                            //6

        x.SetDescription("Sample Topshelf Host");        //7
        x.SetDisplayName("Stuff");                       //8
        x.SetServiceName("stuff");                       //9
    });    

You're going to have to place your file system check BEFORE the above code runs. Let's think about this a second. The point of having a service is to make sure it RUNS and KEEPS RUNNING. You're attempting to subvert a basic principle of having service applications in the first place. Instead of trying to stop the service because of the missing file, figure out some way to alert your support staff and NOT do whatever depends on that missing file.



回答2:

You could use the HostControl object and modify your method like this:

public bool Start(HostControl hostControl)
{
    if (!File.Exists(_xmlFile) 
    {
        hostControl.Stop();
        return true;
    }

    // Do some work here if xml file exists.
    ...
}

And you will need to pass the HostControl in to the Start method like this:

HostFactory.Run(conf =>
{
    conf.Service<YourService>(svcConf =>
    {
        svcConf.WhenStarted((service, hostControl) =>
        {
            return service.Start(hostControl);
        }
    }
}


回答3:

Each of the WhenXxx methods can also take an argument of the HostControl interface, which can be used to request the service be stopped, request additional start/stop time, etc.

In such case, change signature of start() to be bool start(HostControl hc). Retain reference to this HostControl in the service as follow:

public bool Start(HostControl hc)
{
    hostControl = hc;
    Restart();
    return true;
}

Now when you want to stop the service use following call:

hostControl.Stop();


回答4:

I was curious about this from the point of view of a best practice or recommendation in Topshelf's documentation, but couldn't find anything. I did, however, find two separate comments from phatboyg...

Best comment... how to stop service on exception, via this issue (I cut out some of the detail):

If your service's Start method throws an exception, the service will fail to start.

Once the service is running, if an unhandled exception is thrown, the service will stop, and report it as a crash to the service control manager.

If you need to Stop your service programatically, use the HostControl method Stop.

So I think the easiest answer is to throw an exception.

You were doing that, and you mention "the windows service stays around as a process after the exception". That seems like an unrelated bug somewhere in your code, or perhaps you somehow had multple instances running? I've been testing these scenarios this morning and have not seen my service running after throwing an Exception in the start method.

Also, relevant to checking before HostFactory.Run, mentioned in the accepted answer, via https://groups.google.com/forum/embed/#!topic/topshelf-discuss/nX97k3yOhJU:

"Your application should do nothing more than configure NLog/Log4Net before calling the HostFactory.Run() method."



回答5:

I just ran into this issue and all the above answers seem to be over complicating things. All you need to do is use the WhenStarted overload that accepts a Func<T,HostControl,bool> and return false if your internal service bootstrap failed. I don't think hostControl.Stop() needs to be called explicitly.

//Here is bit from inside the .Service<T>() call
s.WhenStarted((YourService svc, HostControl hc) => svc.Start());

//And the svc.Start method would look something like this:
class YourService
{
   public bool Start() {
     //return true if all is well
     //or false if you want service startup to be halted
   }
}


回答6:

When you catch the exception you can use ServiceBase.Stop() Method to stop the service by itself.

try
{
    // Your Code
}
catch (Exception ex)
{
    // The code for stopping service
}

Also you can have multi catch blocks in some cases:

try
{
    // Your Code
}
catch (IndexOutOfRengeException ex)
{
    // The code for stopping service
}
catch (FileNotFoundException exc)
{
    // The code for stopping service
}

Read more about ServiceBase.Stop()



标签: c# topshelf