Click here to Skip to main content
15,867,330 members
Articles / Desktop Programming / WPF

Windows-Service for Multithreading with Remote Control GUI

Rate me:
Please Sign up or sign in to vote.
5.00/5 (25 votes)
25 Feb 2016CPOL8 min read 78.3K   5.2K   85   11
A windows-service which can handle different operations in separate threads and which can be controlled by a small GUI program

Introduction

I was asked to make a Windows Service which handles different tasks in separate threads. The background to this case was that we had several jobs in SQL-Server-Agents (complex data-selections and transfers) which could sometimes take 8 hours+.

Sometimes IIS kicked a job, and we couldn't find a reasonable explanation for why this happened, but we suggested some kind of timeout.

So, the solution was to put those tasks in a Windows Service and let the tasks run with an infinite timeout.

Here is the link to an extension of this article, when you want your service to run AlwaysOn on different servers: AlwaysOn Windows-Service with Remote Control

Background

I was OK with the service, but it bothered me that it runs in the background and no one knew what was going on.

So I came up with the idea to make a small application which shows me the status of the service, the current tasks, and furthermore lets me shut down safely, while storing the current state of the tasks.

GUI program

1. Using the Code

The source is based on Visual Studio 2012 and .NET 4.5. For the GUI, you also need this library: http://wpfanimatedgif.codeplex.com/.

Let's start by creating a new Windows Service project in Visual Studio (for a detailed walkthrough, I found this MSDN article very helpful: http://msdn.microsoft.com/en-us/library/zt39148a%28v=vs.110%29.aspx).

  1. Create the new project of type "Windows Service".
  2. Rename Service1.cs to MyService.cs (including its dependent objects).
  3. Optionally, you can add an EventLog, which I found very helpful. I wrote a small static class Helper\EventLogger.cs which allows me to quickly write to my log (take a look at the MSDN article for a detailed description).

The Event-Logging is part of the source, but not described in this article.

The multi-threading service has three different types of threads:

  • Simple: Can only execute till the end, no parallel simple threads
  • Complex: Can be stopped between several steps and continued at the next start, also no parallel running
  • Multi: Can start N tasks simultaneously

Now we have the base, and I will describe the different parts of the service.

2. Program.cs

This auto-generated class initializes MyService.

3. MyService.cs

Add an interface-class for MyService and call it IMyService.cs.

C#
interface IMyService
{
    void StartService();
    void StopService();
}

Now, open the code-base of MyService and include the Interface and implement its members and create the property:

C#
private bool finalExit { get; set; }
 
public void StartService()
{
    OnStart(null);
}
 
public void StopService()
{
    finalExit = false;
    OnStop();
} 

Create a new folder named WCF and add a class WCFProvider.cs, also add a class ServiceExecution.cs to the root of the project. Now, we modify the initializer of MyService.cs" and add another property.

GetInstance isn't implemented by now, but this will be done in step 6.

C#
private WCFProvider wcfProvider { get; set; }
 
public MyService()
{    
    try
    {
        InitializeComponent();
 
        finalExit = true;
 
        ServiceExecution.GetInstance().myService = this;
 
        wcfProvider = new WCFProvider();
    }
    catch (Exception)
    { }
}  

4. ThreadHolder.cs

Create a new folder called Threading and a class ThreadHolder.cs.

C#
class ThreadHolder
{
    public Thread thread { get; set; }
    public MyThread myThread { get; set; }
    public bool resuming { get; set; }
 
    public ThreadHolder()
    {
        resuming = false;
    }
 
    public void Process()
    {
        myThread.Process(resuming);
    }
} 

The resuming property is used for complex threads with more than one step, to resume an execution at a certain point, after the Service has been stopped before.

MyThread will be available in step 7.

5. LockHolder.cs

To obtain a secure lock on a multi-threaded class, I found the article of Bill Wagner very helpful, and used his technique for my service: More Effective C#: Item 13: Use lock() As Your First Choice for Synchronization.

I placed the "LockHolder.cs" in the Helper-Folder.

6. ServiceExecution.cs

This class is the main thread which handles all the jobs, puts them in separate threads and removes the specific thread, when a job is finished.

6.1 The Enums

The State indicates at which state the ServiceExecution thread is currently operating. When the state is Stopped, the Windows Service itself is still running, and can be restarted with the GUI program.

The ThreadType is used to identify the different jobs.

C#
public enum State
{
    Running,
    Shutting_Down,
    Stopped
}
 
public enum ThreadType
{
    SimpleThread,
    ComplexThread,
    MultiThread
} 

6.2 This Class Should be a threadsave Singleton

This class is accessed from:

  • The GUI
  • The main-class MyService.cs of the service
  • Each job-thread calls back when it is finished

So we need to make sure only one instance exists:

C#
private ServiceExecution() { }
private static ServiceExecution instance;
private static readonly object myInstanceLock = new object();
 
public static ServiceExecution GetInstance()
{
    // DoubleLock for thread safety
    if (instance == null)
    {
        lock (myInstanceLock)
        {
            if (instance == null)
            {
                instance = new ServiceExecution();
            }
        }
    }
    return instance;
} 

6.3 Other Objects

The interface is passed from MyService.cs to call the Start and Stop functions via the WCF Service:

C#
public IMyService myService { get; set; }

Every job which is executed as a thread is stored in the ThreadHolder with a GUID as the unique identifier, to have a simple access to the thread, when closing them, or checking for active threads.

C#
private readonly Dictionary<Guid, ThreadHolder> runningThreads = new Dictionary<Guid, ThreadHolder>(); 

And some properties:

C#
private State currentState = State.Stopped;
 
private DateTime nextRunSimple = DateTime.Now;
private DateTime nextRunComplex = DateTime.Now;
private DateTime nextRunMulti = DateTime.Now;
 
private bool isProcessingSimple;
private bool isProcessingComplex; 

6.4 Initializing

While the ServiceExecution thread is running, it checks periodically if a new child-thread should be started. The logic will be described in 2.5.

When it receives the command to shut down, it waits until all the current threads have finished.

In case of the complex-thread, the order to break the current execution is sent. In my project, this thread consists of four stored-procedure calls. When the break-operation-command is sent at step 2, the thread finishes the second step and writes the information to the database, so that it returns at step 3 when the service is starting again.

C#
public void StartServiceExecution()
{
    try
    {
        currentState = State.Running;
 
        while (currentState == State.Running)
        {
            CheckForSimpleRun();
 
            CheckForComplexRun();
 
            CheckForMultipeRun();
 
            Thread.Sleep(10000);
        }
 
        while (currentState == State.Shutting_Down)
        {
            using (LockHolder<Dictionary<Guid, ThreadHolder>> lockObj =
                new LockHolder<Dictionary<Guid, ThreadHolder>>(runningThreads, 1000))
            {
                if (lockObj.LockSuccessful)
                {
                    foreach (ThreadHolder currentThread in runningThreads.Values)
                    {
                        if (currentThread.myThread.GetThreadType() == ThreadType.ComplexThread)
                        {
                            currentThread.myThread.BreakOperation();
                        }
                    }
 
                    if (runningThreads.Count == 0)
                    {
                        currentState = State.Stopped;
                    }
                }
            }
        }
    }
    catch (Exception)
    { }
}  

6.5 Checking for New Threads

I only add the simple-thread here, hence the other ones are similar (you get them with the source). If no simple-thread is running and the delay time is okay, a new thread will be added to the pool. The thread-class itself will be described in step 7.

C#
private void CheckForSimpleRun()
{
    if (isProcessingSimple == false && nextRunSimple <= DateTime.Now)
    {
        const ThreadType threadType = ThreadType.SimpleThread;
        ThreadHolder exportThread = new ThreadHolder();
        Guid guid = Guid.NewGuid();
        exportThread.myThread = new MyThreadSimple(this, guid, threadType);
 
        if (CreateWorkerThread(exportThread, threadType, guid))
        {
            isProcessingSimple = true;
        }
    }
} 

6.6 Create a New Thread

The runningThreads property should always be accessed thread-safe. If the property can be claimed in a reasonable time (1000 ms), the new thread will be added and started right away.

C#
private bool CreateWorkerThread(ThreadHolder exportThread, ThreadType threadType, Guid guid)
{
    using (LockHolder<Dictionary<Guid, ThreadHolder>> lockObj =
        new LockHolder<Dictionary<Guid, ThreadHolder>>(runningThreads, 1000))
    {
        if (lockObj.LockSuccessful)
        {
            Thread thread = new Thread(exportThread.Process) { Name = threadType.ToString() };
            exportThread.thread = thread;
 
            runningThreads.Add(guid, exportThread);
 
            thread.Start();
 
            return true;
        }
    }
 
    return false;
}

6.7 Clean-Up Finished Threads

Each child-thread inherits the IServiceExecution interface and calls this function when it's done. The properties for the depending thread-type will be reset and the thread will be removed from the pool:

C#
public void ThreadFinished(Guid threadId)
{
    using (LockHolder<Dictionary<Guid, ThreadHolder>> lockObj =
        new LockHolder<Dictionary<Guid, ThreadHolder>>(runningThreads, 1000))
    {
        if (lockObj.LockSuccessful)
        {
            if (runningThreads[threadId].myThread.GetThreadType() == ThreadType.SimpleThread)
            {
                nextRunSimple = DateTime.Now.AddSeconds(Properties.Settings.Default.trigger_simple_thread);
                isProcessingSimple = false;
            }
 
            runningThreads.Remove(threadId);
        }
    }
} 

6.8 Get the Info from Running Threads

This function iterates through all active threads and concatenates the info to a string. I used this way, because it's a simple way to pass data via the WCF service.

C#
public string GetCurrentThreadInfo()
{
    using (LockHolder<Dictionary<Guid, ThreadHolder>> lockObj =
        new LockHolder<Dictionary<Guid, ThreadHolder>>(runningThreads, 1000))
    {
        if (lockObj.LockSuccessful)
        {
            int counter = 0;
            string threadInfo = "";
 
            foreach (KeyValuePair<Guid, ThreadHolder> currentThread in runningThreads)
            {
                DateTime tmpTime = currentThread.Value.myThread.GetStartTime();
 
                if (counter == 0)
                {
                    threadInfo = String.Format("{0:dd.MM.yyyy HH:mm:ss}", tmpTime) + "§" +
                        currentThread.Value.myThread.GetThreadType().ToString();
                }
                else
                {
                    threadInfo = threadInfo + "#" + 
                        String.Format("{0:dd.MM.yyyy HH:mm:ss}", tmpTime) + "§" +
                        currentThread.Value.myThread.GetThreadType().ToString();
                }
 
                counter++;
            }
 
            return threadInfo;
        }
    }
 
    return "";
} 

6.9 Stop Execution

Finally for now, the service-execution-stopping has to be implemented.

C#
public void StopServiceExecution()
{
    currentState = State.Shutting_Down;
} 

6.10 IServiceExecution.cs

This interface is used to provide the necessary functions to the child-threads and the WCFProvider.

C#
interface IServiceExecution
{
    void StartServiceExecution();
 
    void StopServiceExecution();
 
    void ThreadFinished(Guid threadId);
} 

7 The Threading Classes

7.1 The base-class "MyThread.cs"

This class provides the functions which every threading-class needs to implement. The thread ID is the unique identifier for each thread, which is used by the ServiceExecution to remove a thread-object when it's finished.

The startTime is used for the GUI:

C#
protected IServiceExecution serviceExecution;
protected Guid threadId;
protected ThreadType threadType;
protected DateTime startTime;
 
public MyThread(IServiceExecution serviceExecution, Guid threadId, ThreadType threadType)
{
    this.serviceExecution = serviceExecution;
    this.threadId = threadId;
    this.threadType = threadType;
} 

The process function calls the processing routines. Depending on your thread type, the corresponding function can be overridden in your derived classes which inherit from this base class. These functions are protected, hence no other class needs to access them.

C#
public void Process(bool resumeProcessing = false)
{
    startTime = DateTime.Now;
 
    try
    {
        if (resumeProcessing)
        {
            ResumeProcessingData();
        }
        else
        {
            ProcessingData();
        }
    }
    catch (Exception e)
    {
        //...
    }
    finally
    {
        serviceExecution.ThreadFinished(threadId);
    }
}

The BreakOperation is the one which is used in a complex thread to stop the operation and continue at a later run.

C#
public virtual void BreakOperation() { } 

Finally, the class contains getter functions for the thread type and the start time.

7.2 MyThreadComplex.cs

In the sample source, I have only included a short implementation for the complex thread to show the break-operation, the other two thread-classes only implement the base-class without overriding anything.

The processing functions simply call the worker-function in this example.

When BreakOperation is called, the current step of the worker-function will be finished and after that, the function will stop. In the real implementation, I store the current step in the database and when the service is starting again, this saved step will be passed with the property resumeStep.

C#
private readonly int resumeStep;
private bool breakOperation;
 
public MyThreadComplex(IServiceExecution serviceExecution, 
       Guid threadId, ThreadType threadType, int resumeStep = 0)
       : base(serviceExecution, threadId, threadType)
{
    this.resumeStep = resumeStep;
}
 
public override void BreakOperation()
{
    breakOperation = true;
}
 
private void Worker()
{
    for (int i = resumeStep; i < 5; i++)
    {
        Thread.Sleep(7500);
 
        if (breakOperation)
        {
            return;
        }
    }
} 

8 The WCF Service

8.1 IServiceWCF.cs

The interface defines the functions which will be used by the GUI to communicate with the service.

For the WCF functionality, we need to add a reference to the System.ServiceModel assembly.

  • IsServiceActive returns the state of the service
  • Start and Stop are implemented with the IsOneWay-Behaviour, because the GUI shouldn't wait for a response as it is a void call
  • GetActiveThread returns the info about all running threads in a concatenated string
C#
using System.ServiceModel;
 
[ServiceContract]
public interface IServiceWCF
{
    [OperationContract]
    State IsServiceActive();
 
    [OperationContract(IsOneWay = true)]
    void StartImosExportService();
 
    [OperationContract(IsOneWay = true)]
    void StopImosExportService();
 
    [OperationContract]
    string GetActiveThreads();
} 

8.2 ServiceWCF.cs

This class contains the implementation for the interface-class:

  • ConcurrencyMode is set to Multiple, which means multiple calls can occur at the same time. For example: the StopService can be called while asking for ActiveThreads at the same time.
  • The SynchronizationContext is set to false, because different threads will be accessed.
C#
[ServiceBehavior(UseSynchronizationContext=false, ConcurrencyMode=ConcurrencyMode.Multiple)]
public class ServiceWCF : IServiceWCF
{
    public State IsServiceActive()
    {
        return ServiceExecution.GetInstance().CheckIfActive();
    }
 
    public void StartImosExportService()
    {
        ServiceExecution.GetInstance().myService.StartService();
    }
 
    public void StopImosExportService()
    {
        ServiceExecution.GetInstance().myService.StopService();
    }
 
    public string GetActiveThreads()
    {
        string threadInfo = ServiceExecution.GetInstance().GetCurrentThreadInfo();
        return threadInfo;
    }
} 

8.3 WCFProvider.cs

In this class, the WCF service is set up. As I only use the GUI on the same server where the service is running, I used a simple named pipe endpoint, but you can implement all types of WCF communication.

C#
readonly ServiceHost serviceProvider;
 
public WCFProvider()
{
    serviceProvider = new ServiceHost(
        typeof(ServiceWCF), new[] { new Uri("net.pipe://localhost/MyService/") });
 
    serviceProvider.AddServiceEndpoint(typeof(IServiceWCF),
        new NetNamedPipeBinding(), "PipeForMyService");
 
    serviceProvider.Open();
}
 
public void StopProvidingService()
{
    serviceProvider.Close();
}

9 The GUI

Now, just add a second project to the solution for the GUI part. I use WPF, but I won't go much into design details, hence you can use whatever your favorite is, as long as you can consume a WCF service.

Again, we need a reference to the System.ServiceModel assembly.

Then, we can add our mappers to the service:

  • enum for the service-state
  • The interface for the WCF functions
C#
public enum State
{
    Running,
    Shutting_Down,
    Stopped
}
 
[ServiceContract]
public interface IServiceWCF
{
    [OperationContract]
    State IsServiceActive();
 
    [OperationContract(IsOneWay = true)]
    void StartImosExportService();
 
    [OperationContract(IsOneWay = true)]
    void StopImosExportService();
 
    [OperationContract]
    string GetActiveThreads();
}

In the main class, we now need a property for the interface.

After initializing, we will connect to the service via the named pipe provider - see 8.3 as reference.

C#
public MainWindow()
{
    InitializeComponent();
 
    ChannelFactory<IServiceWCF> pipeFactory =
        new ChannelFactory<IServiceWCF>(
            new NetNamedPipeBinding(),
            new EndpointAddress("net.pipe://localhost/MyService/PipeForMyService"));
 
    pipeProxy = pipeFactory.CreateChannel();
} 

Now, we can call the service functions simply with the pipeProxy.

C#
pipeProxy.StartImosExportService();
 
...
 
string threadInfo = pipeProxy.GetActiveThreads();

Future Improvements

We have a possible task in the pipeline, where we should transfer 1..n files one after another. Each file will be imported by an external system and the import is triggered when the file is sent. But one import must have been finished, until the next file can be sent.

So I think of integrating a WCF WebService and a new thread type, which is able to halt after a step, and waits until it gets triggered to continue. Then the external app will call the webservice and the thread will send the next file.

History

  • 16.10.2013 - Initial publication
  • 25.02.2016 - Link to AlwaysOn extension

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Austria Austria
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
BugFinished thread might not get closed Pin
Member 1328921811-Sep-17 6:20
Member 1328921811-Sep-17 6:20 
SuggestionA Low Maintenance XML Settings Mechanism Pin
RickZeeland20-May-17 20:40
mveRickZeeland20-May-17 20:40 
QuestionMemory Process Thread x Running Threads Pin
A@201827-Apr-16 15:26
A@201827-Apr-16 15:26 
AnswerRe: Memory Process Thread x Running Threads Pin
J.Starkl28-Apr-16 1:41
J.Starkl28-Apr-16 1:41 
Hi
Simply set the GUID which I use to use to identify as name of the thread, then you can easily compare them

In the function "CreateWorkerThread" replace
C#
Thread thread = new Thread(exportThread.Process) { Name = threadType.ToString() };

with

Thread thread = new Thread(exportThread.Process) { Name = guid.ToString() };


Although, if you use the processing-method I advice, I wonder why a Thread will stay in runningThreads anyways

C#
public void Process(bool resumeProcessing = false)
{
    try
    {
        //...
    }
    catch (Exception e)
    {
        //...
    }
    finally
    {
        serviceExecution.ThreadFinished(threadId);
    }
}


Kind regards
Jürgen
GeneralRe: Memory Process Thread x Running Threads Pin
A@201828-Apr-16 3:58
A@201828-Apr-16 3:58 
GeneralRe: Memory Process Thread x Running Threads Pin
J.Starkl28-Apr-16 20:46
J.Starkl28-Apr-16 20:46 
GeneralRe: Memory Process Thread x Running Threads Pin
A@201829-Apr-16 12:05
A@201829-Apr-16 12:05 
GeneralRe: Memory Process Thread x Running Threads Pin
J.Starkl3-May-16 22:34
J.Starkl3-May-16 22:34 
QuestionUsing of Task instead of Thread Pin
Gopi Kishan Mariyala21-Mar-16 19:16
Gopi Kishan Mariyala21-Mar-16 19:16 
AnswerRe: Using of Task instead of Thread Pin
J.Starkl29-Mar-16 21:53
J.Starkl29-Mar-16 21:53 
QuestionFew questions Pin
Tridip Bhattacharjee17-Dec-14 20:40
professionalTridip Bhattacharjee17-Dec-14 20:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.