Click here to Skip to main content
15,885,537 members
Articles / Programming Languages / C#
Tip/Trick

Locking for internal operations

Rate me:
Please Sign up or sign in to vote.
4.43/5 (4 votes)
1 Apr 2013CPOL3 min read 20.9K   4   13
Pausing external operations on an object when you want to lock the object for internal operations.

Introduction 

There are times when you want to stop the external operations on an object to do internal operations, say for example you need to cleanup internal data structures to free up memory so you need to halt the use of the object in the meantime.

Although we are using locks to do this we are not blocking the external operations of the object so the object will retain multi-thread support and the operations are concurrent.

Using the code 

Below is the class using this technique. We have a class with three external methods: dash, dot, and freeup, when freeup is called the other two methods will block until done so you are sure that freeup can do all that it needs without worrying about concurrency issues. 

All you need to do for your own classes is to add the CheckLock method at the start of your own methods, so there is minimal changes to the existing code.

C#
class someclass
{
    private bool _internalOperation; // flag for checking if we need to lock
    private object _lock = new object();

    public void dash()
    {
        CheckLock(); // blocks until internal operation is done
        Console.Write("-");
    }

    public void dot()
    {
        CheckLock(); // just add this line to your own code
        Console.Write(".");
    }

    public void freeup()
    {        
        lock (_lock)
        {
            _internalOperation = true;
            // some really important operation here
            for (int i = 0; i < 100; i++)
            {
                Console.Write("F");
                Thread.Sleep(20);
            }
            _internalOperation = false;
        }        
    }

    private void CheckLock()
    {
        if (_internalOperation) // don't lock unless needed
            lock (_lock) ; // the good stuff is here
    }
} 

As you can see we are using locks in the freeup method but not in the other methods so we retain concurrency of operations on the object i.e., dash and dot do not block each other.

To illustrate this below is the sample code that uses the class above:

C#
class Program
{
    public delegate void MethodCall();
    public static bool end = false;

    static void Main(string[] args)
    {
        someclass s = new someclass();

        Task.Factory.StartNew(() =>
        {
            while (!end)
            {
                Thread.Sleep(2000);
                s.freeup();
            }
        });

        Task.Factory.StartNew( () => Exec(s.dot) );

        Task.Factory.StartNew( () => Exec(s.dash) );

        Task.Factory.StartNew(() =>
        {
            Console.ReadKey();
            end = true;
        });

        while (!end)
            Thread.Sleep(1000);
    }

    private static void Exec(MethodCall method)
    {
        while (!end)
        {
            method();
            Thread.Sleep(5);
        }
    }
} 

The above code uses .NET 4 Task framework and starts four threads: one to call the dash, one for the dot, one to exit the program if you press any key, and one to call the freeup every two seconds. So you will see a series of '-' and '.' in the console output with a series of 'F' characters which show that the object is multi-threaded and blocks for the internal operation so you don't see anything break up the 'F' characters.

v2 -  mindful of race conditions

To get around race conditions and to make the use of the code easier I have changed the code to use a helper class and a queue as below:

C#
    class someclass
    {
        // add this class to your own code
        class L : IDisposable
        {
            someclass _sc;
            public L(someclass sc) // change to the class name
            {
                _sc = sc;
                _sc.CheckLock();
            }
            void IDisposable.Dispose()
            {
                _sc.Done();
            }
        }
        private volatile bool _internalOperation;
        private Queue _que = new Queue(); // added a queue
        private object _lock = new object();

        public void dash()
        {
            using (new L(this)) // better and cleaner way to wrap things
            {
                // your normal code goes here
                Console.Write("-");
            }
        }

        public void dot()
        {
            using (new L(this))
            {
                Console.Write(".");
            }
        }

        public void freeup()
        {
            lock (_lock)
            {
                _internalOperation = true;
                while (_que.Count > 0) Thread.SpinWait(1); // wait till pending operations are done

                for (int i = 0; i < 100; i++)
                {
                    Console.Write("F");
                    Thread.Sleep(20);
                }
                _internalOperation = false;
            }
        }

        private void CheckLock()
        {
            if (_internalOperation)
                lock (_lock) ;
            _que.Enqueue(1);
        }

        private void Done()
        {
            if (_que.Count > 0)
                _que.Dequeue();
        }
    }

A couple of things to do in your own code and you are good to go:

  • Add the L class to your own code.
  • Change the class type passed to the L class (i.e. someclass to your own class).
  • Add a Queue to your main class.
  • Wrap your public methods with the using(new L(this)) line.
The queue ensures that all pending operations are completed before the freeup() starts and all public operations stop in the meantime.

Points of Interest

Personally I used to use Thread.Sleep a lot for these types of checking which took a performance toll and looked really ugly, and while thinking about it I came to this solution which seems much cleaner.

The if statement in CheckLock insures that you do not take a performance hit in locking when you don't need to.

A very important point is that the public methods on the main class are concurrent and do not lock each other out so you can call them concurrently.

History 

  • Initial release: 28th March 2013.
  • Update :  31st March 2013
    • Moved changing _internalOperation into the lock in freeup()
  • Update : 1st April 2013
    • New symantics using a queue and a helper class

License

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


Written By
Architect -
United Kingdom United Kingdom
Mehdi first started programming when he was 8 on BBC+128k machine in 6512 processor language, after various hardware and software changes he eventually came across .net and c# which he has been using since v1.0.
He is formally educated as a system analyst Industrial engineer, but his programming passion continues.

* Mehdi is the 5th person to get 6 out of 7 Platinum's on Code-Project (13th Jan'12)
* Mehdi is the 3rd person to get 7 out of 7 Platinum's on Code-Project (26th Aug'16)

Comments and Discussions

 
QuestionShould internalOperation be volatile? Pin
FatCatProgrammer2-Apr-13 5:20
FatCatProgrammer2-Apr-13 5:20 
AnswerRe: Should internalOperation be volatile? Pin
Andrew Rissing2-Apr-13 5:52
Andrew Rissing2-Apr-13 5:52 
GeneralMy vote of 1 Pin
Andrew Rissing1-Apr-13 6:22
Andrew Rissing1-Apr-13 6:22 
GeneralRe: My vote of 1 Pin
Mehdi Gholam1-Apr-13 9:19
Mehdi Gholam1-Apr-13 9:19 
GeneralRe: My vote of 1 Pin
Andrew Rissing2-Apr-13 5:47
Andrew Rissing2-Apr-13 5:47 
GeneralRe: My vote of 1 Pin
Mehdi Gholam2-Apr-13 7:28
Mehdi Gholam2-Apr-13 7:28 
GeneralRe: My vote of 1 Pin
Andrew Rissing2-Apr-13 8:01
Andrew Rissing2-Apr-13 8:01 
BugUnintended side effects... Pin
Andrew Rissing29-Mar-13 6:40
Andrew Rissing29-Mar-13 6:40 
GeneralRe: Unintended side effects... Pin
Mehdi Gholam29-Mar-13 20:09
Mehdi Gholam29-Mar-13 20:09 
GeneralRe: Unintended side effects... Pin
Mehdi Gholam30-Mar-13 21:16
Mehdi Gholam30-Mar-13 21:16 
BugRe: Unintended side effects... Pin
Andrew Rissing1-Apr-13 6:21
Andrew Rissing1-Apr-13 6:21 
SuggestionReaderWriterLockSlim Pin
Andrew Rissing29-Mar-13 6:22
Andrew Rissing29-Mar-13 6:22 
GeneralRe: ReaderWriterLockSlim Pin
Mehdi Gholam29-Mar-13 20:12
Mehdi Gholam29-Mar-13 20:12 

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.