Introduction
It's a normal situation when you need to control execution of parallel threads but don't want (or can't) pass additional arguments like
CancellationTokenSource
or similar objects into your code. This article describes
a simple solution that allows to associate a cancellation token like object with
a thread.
Background
For example let's imagine that you have some IAlgorithm
interface like this one:
public interface IAlgorithm
{
void PrepareData(Func<object> func);
}
and you need some ability to stop execution of your function that you are passing as argument to PrepareData
:
public object MyFunctionToPrepareData()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Step {0}", i);
Thread.Sleep(1000);
}
return "Data for my algorithm.";
}
IAlgorithm algorithm = new MyAlgorithm();
algorithm.PrepareData(MyFunctionToPrepareData);
To pass CancellationTokenSource
into MyFunctionToPrepareData
you, obviously, have to change IAlgorithm
and add one more parameter to
the MyFunctionToPrepareData
function. That is not always possible.
It'll look like this:
public interface IAlgorithm
{
void PrepareData(Func<CancellationTokenSource, object> func);
}
public static object MyFunctionToPrepareData(CancellationTokenSource src)
{
for (int i = 0; i < 10; i++)
{
if (src.IsCancellationRequested)
Thread.CurrentThread.Abort();
Console.WriteLine("Step {0}", i);
Thread.Sleep(1000);
}
return "Data for my algorithm";
}
IAlgorithm algorithm = new MyAlgorithm();
CancellationTokenSource tokenSource = new CancellationTokenSource();
algorithm.PrepareData(tokenSource, MyFunctionToPrepareData);
In such a situation it can be very convenient to associate a cancellation flag with
the current thread. You can do this with a tiny class described in this article.
Using the Code
The following class solves the described problem by binding thread cancellation info to
the execution thread.
public class CancellationScope: IDisposable
{
[ThreadStatic]
private static List<CancellationScope> _threadActiveScopes;
public CancellationScope()
{
ThreadActiveScopes.Add(this);
}
public bool IsCancellationRequested { get; private set; }
public void Cancel()
{
IsCancellationRequested = true;
}
public static void TryContinue()
{
foreach (CancellationScope scope in ThreadActiveScopes)
{
if (scope.IsCancellationRequested)
Thread.CurrentThread.Abort();
}
}
private static List<CancellationScope> ThreadActiveScopes
{
get
{
if (_threadActiveScopes == null)
_threadActiveScopes = new List<CancellationScope>();
return _threadActiveScopes;
}
}
public void Dispose()
{
ThreadActiveScopes.Remove(this);
}
}
As you can see this class allows to associate one or more CancellationScope
instances with
the current thread. Association achieved with the help of ThreadStatic
attribute
that guarantees that every thread will have own instance of CancellationScope
list.
So now everything that you need to check if the cancellation has been requested by
the main flow is to call the CancellationScope.TryContinue()
static function
at some checkpoints. It'll get all CancellationScope
instances associated with
the current thread, and check the IsCancellationRequested
property
of these instances. To cancel a background task from your foreground code it's enough to call
the myCurrentScope.Cancel()
method.
Now the MyFunctionToPrepareData
function supports the cancellation without any additional arguments:
public object MyFunctionToPrepareData()
{
for (int i = 0; i < 10; i++)
{
CancellationScope.TryContinue();
Console.WriteLine("Step {0}", i);
Thread.Sleep(1000);
}
return "Data for my algorithm";
}
And my background code:
IAlgorithm algorithm = new MyAlgorithm();
using (CancellationScope scope = new CancellationScope())
{
algorithm.PrepareData(MyFunctionToPrepareData);
}
See all source code and usage example in attachments.
Points of Interest
You can also extend CancellationScope
with some event (and TryContinue
with
a state object argument) to pass current execution progress to the foreground thread.
Please also take into account that CancellationScope
should be created and destroyed only in
the thread that you want to control.
History
- Version 1 - 9 Feb 2013 - First version.
- Version 2 - 17 Feb 2013 - Dictionary replaced with ThreadStatic attribute. It allows to simplify code, and get rid of lock statements.
Software Architect - Net. C#, JavaScript
Web Site: http://icocentre.com/