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
}