Basic design pattern for using TPL inside windows service for C# Basic design pattern for using TPL inside windows service for C# multithreading multithreading

Basic design pattern for using TPL inside windows service for C#


Caveat here is not only how you start your worker, but also how you actually stop it.

Beside Task itself, TPL also provides you with a very convenient way of task cancellation by using CancellationToken and CancellationTokenSource objects.

Starting and stopping windows service with TPL

To apply this technique to your Windows Service, you basically need to do the following:

    private CancellationTokenSource tokenSource;    private Task backgroundTask;    protected override void OnStart(string[] args)    {        tokenSource = new CancellationTokenSource();        var cancellation = tokenSource.Token;        backgroundTask = Task.Factory.StartNew(() => WorkerThreadFunc(cancellation),                                    cancellation,                                    TaskCreationOptions.LongRunning,                                    TaskScheduler.Default);    }

Notes:

  • TaskCreationOptions.LongRunning hints a task scheduler that this task may require a separate thread and avoids putting it on a ThreadPool (so that it won't block other items in a pool)
  • CancellationToken is passed to WorkerThreadFunc because this is the way it will be notified to stop its work - see Cancellation section below

And in your OnStop method, you trigger cancellation and wait for task to complete:

    protected override void OnStop()    {        bool finishedSuccessfully = false;        try        {            tokenSource.Cancel();            var timeout = TimeSpan.FromSeconds(3);            finishedSuccessfully = backgroundTask.Wait(timeout);        }        finally        {            if (finishedSuccessfully == false)            {                // Task didn't complete during reasonable amount of time                // Fix your cancellation handling in WorkerThreadFunc or ProcessPicture            }        }    }

Cancellation

By calling tokenSource.Cancel(); we simply tell every CancellationToken issued by this tokenSource to become cancelled and every method that accepted such token (like your WorkerThreadFunc) should now be stopping its work.

Handling cancellation is specific to the implementation, but general rule is that your method should be monitoring cancellation token state and be able to stop its work in a reasonable amount of time. This approach requires you to logically split your work into smaller parts so that you won't stuck at doing some work that needs a lot of time to complete and won't start any new work if cancellation was requested.

Looking at your WorkerThreadFunc code, you may consider to check for cancellation before you execute every new ProcessPicture task, for example:

private List<Task> tasks = new List<Task>();private void WorkerThreadFunc(CancellationToken token){    foreach (string path in paths)    {        if (token.IsCancellationRequested)        {            // you may also want to pass a timeout value here to handle 'stuck' processing            Task.WaitAll(tasks.ToArray());            // no more new tasks            break;        }        string pathCopy = path;        var task = Task.Factory.StartNew(() =>            {                Boolean taskResult = ProcessPicture(pathCopy, token); // <-- consider a cancellation here                return taskResult;            }, token); // <<--- using overload with cancellation token        task.ContinueWith(t => result &= t.Result);        tasks.Add(task);    }}

If ProcessPicture takes very long time to complete, you may also want to add cancellation support in there. Similarly to WorkerThreadFunc, you should take into account ProcessPicture implementation. The key idea here is to find a place where you can safely stop doing work and return from method. By safely I mean - without leaving system or data in a broken state.

In addition to monitoring IsCancellationRequested in WorkerThreadFunc, you can also Register a callback that will be executed when cancellation is requested, to do some other stuff like cleanup, etc:

token.Register(CancellationCallback);

And

private void CancellationCallback(){    // wait for all tasks to finish    // cleanup}