Click here to Skip to main content
15,881,882 members
Articles / General Programming / Threads

Running Services in Long Lived Applications in .NET

Rate me:
Please Sign up or sign in to vote.
4.14/5 (3 votes)
24 Jun 2014LGPL34 min read 8.2K   9  
Running services in long lived applications in .NET

You have probably created a Windows service or other types of applications where you have code which needs to be run in the background to not block the main thread. To do that, you have different types of solutions in .NET, for instance Threads, Timers and Tasks. However, you need to construct them manually, make sure that they do not throw unhandled exceptions, etc.

Griffin.Framework provides a simpler solution where you only need to implement a small interface. The library also has full support for your favorite inversion of control container, which enables to use dependency injection in your application services.

Application Services

Application services are classes that should be treated as single instances, i.e., they are created and started when your application is started and are stopped and disposed when your application is stopped.

This library has two different base classes for application services. Either inherit one of them or just implement the interface IApplicationService.

To enable IoC support, you either have to install one of our IoC adapter packages from nuget or implement two interfaces for your favorite container.

IApplicationService

This contract allows you to start background services when your application starts and stop them when the application ends. They are treated as single instances and are also monitored by this library (if they implement IGuardedService).

C#
public class RequestQueueReader : IApplicationService
{
	private readonly ILog _logger = LogManager.GetLogger(typeof (RequestQueueReader));
	private readonly IContainer _container;
	private readonly QueueReader _reader;
	private int _retryCounter;

	public RequestQueueReader(IContainer container)
	{
		_container  container;

		var queueName = ConfigurationManager.AppSettings["RequestQueue.Name"];
		if (queueName == null)
			throw new ConfigurationErrorsException(
				"Did not find 'RequestQueue.Name' in appSettings. Configure it.");

		_reader = new QueueReader(queueName, new XmlMessageSerializer(new[] {typeof (RequestMsg)}));
		_reader.MessageReceived += OnMessageRead;
		_reader.Faulted += OnFaulted;
	}

	public void Start()
	{
		_reader.Start();
	}

	public void Stop()
	{
		_reader.Stop();
	}

	private void OnMessageRead(object sender, MessageReceivedEventArgs e)
	{
		var message = (Anno) e.Message;
		_annoMessageHandler.Handle(message);
		_retryCounter = 0;
	}
}

ApplicationServiceThread

A base class which uses a Thread for your processing. It will also take care of starting/stopping your service and handle uncaught exceptions. If an uncaught exception is detected, it will shutdown and wait for the ApplicationServiceManager to restart it.

C#
public class YourService : ApplicationserviceThread
{
    protected void Run(WaitHandle shutdownHandle)
    {
        while (true)
        {
            try
            {
                // pause 100ms between each loop iteration.
                // you can specify 0 too
                if (shutdownHandle.WaitOne(100))
                    break;

                // do actual logic here.
            } 
            catch (DataException ex)
            {
                // no need for try/catch, but we only want the service
                // to get automatically restarted when DataException is thrown
                throw;
            }
            catch (Exception ex)
            {
                _log.Error("Opps", ex);
            }
        }
    }
}

ApplicationServiceTimer

ApplicationServiceTimer allows you to execute jobs in the background without having to deal with unhandled exceptions.

Do note that instances of this class are also treated as single instances.

C#
public class DeleteOldMessages : ApplicationServiceTimer
{
    public override void Execute()
    {
        using (var connection = new SqlConnection("..."))
        {
            using (var transaction = connection.BeginTransaction())
            {
                //delete query.
            }
        }
    }
}

However, if you are using timers that require a container scope (lifetime scope), you might want to use IBackgroundJob instead. The difference is that the instances of ApplicationServiceTimer are single instances, while background jobs are created/disposed every time they are being executed.

Controlling Services

The classes are controlled by the ApplicationServiceManager. It can both use your favorite IoC container (through the adapter contracts) or by using assembly scanning.

Without a container:

C#
// find your application services
var serviceLocator = new AssemblyScanner();
serviceLocator.Scan(Assembly.GetExecutingAssembly());

// run the services
_serviceManager = new ApplicationServiceManager(serviceLocator);
_serviceManager.Start();

Using an inversion of control container:

C#
// autofac
var builder = new ContainerBuilder();
builder.Register<YourServiceClass>().AsImplementedInterfaces().SingleInstance();
var autofac = builder.Build();

// griffin library
var container = new AutofacAdapter(autofac);
_serviceManager = new ApplicationServiceManager(container);
_serviceManager.Start();

// .. when the application is shut down..
_serviceManager.Stop();

By using a container, you can also use dependency injection in your services.

Detecting Failures

The ApplicationServiceManager will restart services which have failed (as long as they implement IGuardedService). But you probably want to be able to log all failures. This can be done with the help of the ServiceFailed event:

C#
public class Program
{
    public static void Main(string[] argv)
    {
        // [.. other initializations ..]]
        _appServiceManager.ServiceFailed += OnServiceFailure;
    }

    public static void OnServiceFailure(object sender, ApplicationServiceFailedEventArgs e)
    {
        _logger.Error("Service " + e.ApplicationService.GetType().Name + " failed", e.Exception);
    }
}

Starting/Stopping Services

The services can for instance be started/stopped using the application config. To activate a service, you just set the appSetting <add key="YourClass.Enabled" value="true" /> and to disable it, you set the same key to false.

This can also be done during run time if your services implement IGuardedService.

Background Jobs

Do you have work which needs to be run occasionally? Do you use an inversion of control container?

Then the background jobs are for you. If not, look at ApplicationServiceTimer instead.

The jobs are run at certain intervals within an isolated child scope in a container. That means that all other jobs will continue to work, even if one of your background jobs fail. It also means that you can use an own transaction for each job.

C#
public class CleanUpOldFriendRequests : IBackgroundJob
{
    private readonly IUnitOfWork _uow;
    private static DateTime _lastExecutionTime;

    public CleanUpOldFriendRequests(IUnitOfWork uow)
    {
        if (uow == null) throw new ArgumentNullException("uow");

        _uow = uow;
    }

    public void Execute()
    {
        //run once a day
        if (_lastExecutionTime.Date >= DateTime.Today)
            return;
        _lastExecutionTime = DateTime.Today;

        using (var cmd = _uow.CreateCommand())
        {
            cmd.CommandText = "DELETE FROM FriendRequests WHERE CreatedAtUtc < @datum";
            cmd.AddParameter("datum", DateTime.Today.AddDays(-10));
            cmd.ExecuteNonQuery();
        }
    }
}

Running the Jobs

The jobs are managed by the BackgroundJobManager class. It takes care of starting the jobs, running them at defined intervals and finally report any errors if they fail.

To get started, you need to create a new instance of the class and provide the IoC adapter that will be used to locate services.

C#
public class Service1 : ServiceBase
{
    BackgroundJobManager _jobInvoker;
    IContainer  _container;

    public Service1()
    {
        _serviceLocator = CreateContainer();

        _jobInvoker = new BackgroundJobManager(_container);
        _jobInvoker.ScopeClosing += OnScopeClosing;
    }

    public override OnStart(string[] argv)
    {
        _jobInvoker.Start();
    }

    public override OnStop()
    {
        _jobInvoker.Stop();
    }

    public void CreateContainer()
    {
        // create your favorite container (this example uses autofac)
        // and register your services in it.
        var builder = new Containerbuilder();
        builder.Register<DeleteOldMessages>().AsImplementedInterfaces().SingleInstance();

        // and then create the griffin adapter
        var autofac = builder.Build();
        _container = new AutofacAdapter(autofac);
    }

    // so that we can commit the transaction
    // event will not be invoked if something fails.
    public void OnScopeClosing(object sender, ScopeCreatedEventArgs e)
    {
        e.Scope.Resolve<IUnitOfWork>().SaveChanges();
    }
}

Controlling Intervals

In the BackgroundJobManager, there are two properties which control how often the jobs are being executed.

Name Description
StartInterval Amount of time before the jobs are executed for the first time (after Start() has been invoked).
ExecuteInterval Interval between every job execution (for a single job). The interval is reset when the job completes its execution.

Logging Errors

Sometimes job fails. You can log all errors by subscribing to an event:

C#
public class Service1 : ServiceBase
{
    BackgroundJobManager _jobInvoker;
    IContainer  _container;

    public Service1()
    {
        _serviceLocator = CreateContainer();

        _jobInvoker = new BackgroundJobManager(_container);
        _jobInvoker.JobFailed += OnBackgroundJobFailure;
    }

    private void OnBackgroundJobFailure(object sender, BackgroundJobFailedEventArgs e)
    {
        _logger.Error("Failed to execute " + e.Job, e.Exception);
    }

    // [....]
}

Summary

The application service classes take care of bootstrapping, exception handling and add dependency injection support for timers and threads. Use them when you need a simple way of building background services in your application.

Install the nuget package griffin.framework to get started.

The documentation is also available at github (scroll down).

Sample project is also available at github.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Founder 1TCompany AB
Sweden Sweden

Comments and Discussions

 
-- There are no messages in this forum --