Tuesday, April 29, 2008

Running Multiple .NET Services within a Single Process

I love the fact that .NET makes it profoundly easy to write Windows Services. Most of the low level details have been abstracted away, and while this makes it easier to write and deploy services, sometimes it doesn't work the way you'd expect. For example, I noticed something odd when I tried to write a Service that hosted multiple services. According to the API, it is possible to provide multiple ServiceBase objects to the static Run method as an array:

public static void Main()
{
 ServiceBase[] ServicesToRun = new ServiceBase[] { new Service1(), new Service2() };
 ServiceBase.Run(ServicesToRun);
}

However, when my code executes, only the first ServiceBase object runs, which seems suspicious. The culprit is that the API is somewhat misleading -- the ServiceBase.Run method is not actually responsible for running your services. Instead, it loads them into memory and then hands them off to the Service Control Manager for activation. The service that gets activated is the service you requested when you activated it from the Services Applet or command line:

NET START Service1

This error has appeared in many different forums, but no one seems to post a working example, so maybe I'm not entirely alone on this one. I think part of the confusion stems from the fact that I can give the first ServiceBase object in the array any ServiceName that I wish and it will execute.

public static void Main()
{
 ServiceBase myService = new Service1();
 MyService.ServiceName = "ServiceA";
 ServiceBase.Run(myService);
}

How to make it work:

The correct way to allow multiple services to run within in a single process requires the following:

  1. An installer class with the RunInstaller attribute set to True. The class is instantiated and invoked when you run InstallUtil.exe
  2. The installer class must contain one ProcessInstaller instance. This object is responsible for defining the operating conditions (Start-up mode and User) that your service application will run under.
  3. The installer class must contain one ServiceInstaller instance per ServiceBase in your application. If you plan on running multiple services, each one must (sadly) be installed prior to use.
  4. For the service that you anticipate being started from the Services Applet, list the other services in the ServicesDependedOn property so that they will be started when your service starts:
[RunInstaller(true)]
public class MyServiceInstaller : Installer
{
 public MyServiceInstaller()
 {
     ServiceProcessInstaller processInstaller = new ServiceProcessInstaller();
     processInstaller.Account = ServiceAccount.LocalSystem;
  
     ServiceInstaller mainServiceInstaller = new ServiceInstaller();
     mainServiceInstaller.ServiceName = "Service1";
     mainServiceInstaller.Description = "Service One";
     mainServiceInstaller.ServicesDependedOn = new string [] { "Service2" };
  
     ServiceInstaller secondServiceInstaller = new ServiceInstaller();
     secondServiceInstaller.ServiceName = "Service2";
     secondServiceInstaller.Description = "Service Two";
  
     Installers.Add(processInstaller);
     Installers.Add(mainServiceInstaller);
     Installers.Add(secondaryServiceInstaller);
 }
}

Now when Service1 starts, Service2 is also started. Happily, both services log to the same log4net file and the number of Processes in the Task Manager increments only by one.

Note that when Service2 is stopped, Service1 will also be stopped. However, shutting down Service2 will not stop Service1. If you want tighter coupling between the two services, you might consider adding ServiceController logic to Service1 to start and stop Service2 during the Service1 OnStart and OnStop methods... maybe something I'll follow up with a later post.

submit to reddit

4 comments:

Anonymous said...

Services should be independent. For something like this it's more appropriate to create a service that creates worker threads to do the work of the "sub" processes.

Shrini said...

Thanks for the clariffication. I was breaking my head why only one instance of the service is starting even though both services were starting even though both were installed.

This worked out nicely and I was able to capture the service specifics from external file during installation.

Shrini Viswanathan

Jean-marc Lai said...

sometimes from a design perspective, having multiple services is preferable to a multi-threaded app. its less coupled and a lot more less complicated.

aMoL said...

This is good solution, previously I tried with this :
1. Created individual ProjectInstaller.cs file for each services
2. Used ProjectInstaller.cs's AddRange method to add ProjectInstaller's instance
But above tactic failed to start both services.