Introduction
This article discusses how to design and develop a Windows polling service that is flexible enough to allow the addition of new tasks in the future.
There are two main parts in this article, the task configuration and creation and execution of these tasks.
This also makes for a good tutorial on how to setup a custom configuration section using Configuration Section Designer and on using the system timer class to poll in a Windows service.
Background
In an enterprise system environment, you often come across the need to execute asynchronous processes/tasks. The most common approach is to create a Windows service that will poll for tasks and execute them. However, this leads to the creation of a number of services over time.
Managing multiple services and ensuring that they are all up and running can be a huge task by itself. Not to forget the development time involved in rewriting the same polling service code every time. This led me to build a generic polling service that could run any task in an orderly manner and be flexible enough to allow for the addition of new tasks with relative ease in the future.
Prerequisites
You will need to download and install the Configuration Section Designer from CodePlex.
Using the Code
This first part describes how to create a configuration section dedicated to configuring tasks. The Configuration Section Designer tool is a nifty little tool that allows you to do much of the heavy lifting easily and I highly recommend it.
Configuration Section
First, we need to create a configuration section to deal with configuring the individual tasks.
Once you create your project, you will need to right click on the project in the Solution explorer and click on the Add New Item.
This will bring up the Add New Item dialogue box. For a default installation of Configuration Section Designer tool, you should be able to choose ConfigurationSectionDesigner
template as illustrated below:
Designing the Configuration Section
Once you have the designer open, you will need to create a Configuration Element by dragging the Configuration Element icon from the Toolbox. Right click on the newly created Configuration Element on the designer and choose Properties from the context menu. Then enter the following properties in the same way as creating the Configuration Section above.
Name - Task
NameSpace - WSPolling
Add the following attributes to the Configuration
Element in the same way as creating the Configuration Section above.
* Please note that the XML Name is case sensitive.
Name | Type | Xml Name | IsKey |
TaskName | String | taskName | True |
TaskOrder | Int32 | taskOrder | |
Enabled | Boolean | enabled | |
Then we need to add a Configuration Element Collection in much the same way as above you can set the properties for the Configuration Element Collection.
Name - Tasks
Namespace - WSPolling
Xml Item Name - task
Item Type - Task
Then we need to add a Configuration Section from the toolbox. In the same way you defined the properties for the Configuration Element, right click on the newly created configuration section element on the designer and choose Properties from the context menu.
In the Properties window, set the following properties:
Name - TaskConfiguration
Namespace - WSPolling
XML Section Name - taskConfiguration
* Please note that the XML Section Name is case sensitive.
Then you need to create an element under Configuration Section and set the following properties.
Name - Tasks
Type - Tasks
Xml Name tasks
The end result of all the configuration should look like the below illustration:
The following code creates a Windows polling service that loads, sorts and executes the various tasks and their attributes.
Required Classes
ITask
– The interface that defines the individual taskTaskManager
– The object that loads, sorts and executes the various individual tasksTask1
- Implementation of the taskTask2
- Implementation of the task
Create a System Timer
We make use of the System.Timer
class to fire events at regular intervals to perform the polling action of the Windows service. You can configure the timer to fire at any given time span - here it is set at 4 secs. First, we instantiate an object of type TaskManager
. Then we load all the enabled tasks by looking up the configuration section in the app.config file.
public void runService()
{
TaskManager tm = new TaskManager();
tm.LoadSortedProviders();
aTimer = new System.Timers.Timer(4000);
this.aTimer.AutoReset = true;
aTimer.Elapsed += new ElapsedEventHandler(tm.ExecuteTasks);
aTimer.Start();
GC.KeepAlive(this.aTimer);
}
TaskManager
This object manages how we load and execute the tasks. This object contains two main methods.
The LoadSortedProviders
method loads all tasks configured under the config section TaskSettings
and then sorts them by TaskOrder
. You can add any additional behaviour you may require in this method.
public void LoadSortedProviders()
{
try
{
foreach (Task tc in config.Tasks.OfType<Task>())
{
ITask t = Activator.CreateInstance(Type.GetType(tc.TaskName)) as ITask;
t.TaskOrder = tc.TaskOrder;
t.Enabled = tc.Enabled;
if (t.Enabled)
{
_cprovider.Add(t);
}
}
_cprovider.Sort(new TaskPriorityComparer());
}
catch (Exception ex)
{
}
}
ExecuteTasks
method executes each task from the ordered tasklist.
public void ExecuteTasks(object source, ElapsedEventArgs e)
{
try
{
foreach (ITask t in _cprovider)
{
t.Execute();
}
}
catch (Exception ex)
{
}
}
Task1/Task 2
These classes contain the actual task implementation. You can extend the polling service by adding classes that implement the ITask
interface and adding the task implementation within the Execute
method of the Task
class. They also need to be configured in the .config file as detailed in the below Task Configuration section:
public void Execute( )
{
Console.WriteLine("Executing task1");
}
Task Configuration
Every task that is added will require the following configuration to be setup in the config file.
taskName
is the fully qualifying name of your Task
object including the namespace, enabled will determine if it is to be run or ignored when the polling service is started, taskOrder
attribute will determine what order the task is executed in. You can add more attributes for your customized requirements.
<tasks>
<task taskName="WSPolling.Task1" enabled="true" taskOrder="0" />
<task taskName="WSPolling.Task2" enabled="true" taskOrder="1" />
</tasks>
Points of Interest
One of the things that I would like to do is try and run these tasks in their own memory space so that they don’t create any dependencies on each other. That is, if any of them does crash, I would like to see the others continue to run.
System.Timer class
will execute the event on a thread that is obtained from the ThreadPool
.