Click here to Skip to main content
Click here to Skip to main content

How to control parallel thread execution without passing CancellationTokenSource or similar objects into your background code

, 11 Feb 2013
Rate this:
Please Sign up or sign in to vote.
This article describes a tiny class that allows to pass a cancellation flag to your background code, without modifying your existing interfaces and function prototypes.
This is an old version of the currently published article.

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
{
    private static Dictionary<int, List<CancellationScope>> _activeCancellationScopes = 
        new Dictionary<int, List<CancellationScope>>();
    private static volatile object _syncRoot = new object();

    public CancellationScope() 
    {
        lock(_syncRoot)
        {
            if (!_activeCancellationScopes.ContainsKey(Thread.CurrentThread.ManagedThreadId))
                _activeCancellationScopes[Thread.CurrentThread.ManagedThreadId] = new List<CancellationScope>();
            _activeCancellationScopes[Thread.CurrentThread.ManagedThreadId].Add(this);
        }
    }

    public bool IsCancellationRequested { get; private set; }

    public void Cancel()
    {
        IsCancellationRequested = true;
    }

    public static void TryContinue()
    {
        lock (_syncRoot)
        {
            List<CancellationScope> scopes;
            _activeCancellationScopes.TryGetValue(Thread.CurrentThread.ManagedThreadId, out scopes);

            if(scopes != null)
                foreach (CancellationScope scope in scopes)
                {
                    if (scope.IsCancellationRequested)
                        Thread.CurrentThread.Abort();
                }
        }
    }

    public void Dispose()
    {
        lock (_syncRoot)
        {
            _activeCancellationScopes[Thread.CurrentThread.ManagedThreadId].Remove(this);
            if (_activeCancellationScopes[Thread.CurrentThread.ManagedThreadId].Count == 0)
                _activeCancellationScopes.Remove(Thread.CurrentThread.ManagedThreadId);
        }
    }
}

As you can see this class allows to associate one or more CancellationScope instances with the current thread. 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);
    // pass scope variable to your foreground thread to allow to cancel execution
}

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.

License

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

Share

About the Author

Volodymyr Bobko

Ukraine Ukraine
Software Architect - Net. C#, JavaScript
Web Site: http://icocentre.com/

Comments and Discussions


Discussions posted for the Published version of this article. Posting a message here will take you to the publicly available article in order to continue your conversation in public.
 
GeneralSmart PinmemberOmar Gameel Salem7-Jan-14 1:13 
QuestionNice trick Pinmembermrchief_200022-Feb-13 7:39 
Question+5 PinmemberMember 45586621-Feb-13 10:04 
GeneralMy vote of 1 PinmemberWilliam E. Kempf11-Feb-13 11:03 
GeneralSmall inefficiency spotted Pinmemberabdurahman ibn hattab11-Feb-13 10:50 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140821.2 | Last Updated 11 Feb 2013
Article Copyright 2013 by Volodymyr Bobko
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid