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 lock
s 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.
class someclass
{
private bool _internalOperation; private object _lock = new object();
public void dash()
{
CheckLock(); Console.Write("-");
}
public void dot()
{
CheckLock(); Console.Write(".");
}
public void freeup()
{
lock (_lock)
{
_internalOperation = true;
for (int i = 0; i < 100; i++)
{
Console.Write("F");
Thread.Sleep(20);
}
_internalOperation = false;
}
}
private void CheckLock()
{
if (_internalOperation) lock (_lock) ; }
}
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:
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:
class someclass
{
class L : IDisposable
{
someclass _sc;
public L(someclass sc) {
_sc = sc;
_sc.CheckLock();
}
void IDisposable.Dispose()
{
_sc.Done();
}
}
private volatile bool _internalOperation;
private Queue _que = new Queue(); private object _lock = new object();
public void dash()
{
using (new L(this)) {
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);
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