Click here to Skip to main content
15,894,343 members
Articles / Programming Languages / C#

Talkative Locker Pattern

Rate me:
Please Sign up or sign in to vote.
4.11/5 (5 votes)
25 Nov 2009CPOL2 min read 20.4K   137   14   3
A design pattern to help debug multi-threaded applications.

Introduction

I’ve been reading many of Martin Fowler’s design patterns (and many other design patterns) lately and have come to appreciate them. What I realize is that a design pattern does not necessarily have to be complex to learn, understand, and apply. The pattern just has to solve a particular problem situation. At the very least, the pattern should help the developer create code that is flexible and easy to modify.

Having said that, I have not seen many widespread patterns that approach the topic of helping developers to debug their multi-threaded programs better. We can all agree that debugging multi-threaded code can easily become a nightmare. Tracking down which threads are currently waiting for a lock, which threads are currently executing, and which threads are about to release a lock can be cumbersome.

Background

I wanted to develop a simple design pattern that could make multi-threading debugging much easier for developers. In this article, I introduce the TalkativeLocker Design Pattern.

Details

The TalkativeLocker Pattern is designed to isolate the locking region of the code from the rest of your code. There is a single point where a lock is entered, acquired, and released. In a way, you are really wrapping the area. By isolating this region, you can write logging and debugging information at each point of the locking and execution stages of any thread that enters.

Here is an extremely simple class that demonstrates the TalkativeLocker Pattern:

C#
public class TalkativeLocker<T>
{
    private readonly static object toLock = new object();

    public T EnterLock(IClient client, Func<T> function)
    {
        T result = default(T);
        Console.WriteLine(client.Name + ":Waiting for lock");

        Monitor.Enter(toLock);

        try
        {
            Console.WriteLine(client.Name + ":AquiredLock");
            result = function();
        }
        catch (Exception ex)
        {
            //
        }
        finally
        {
            Console.WriteLine(client.Name + ":About release lock");
            Monitor.Exit(toLock);
        }
        return result;
    }
}

The class TalkativeLocker contains a private object only used for locking: toLock. Notice that it is static. This means that it will be shared. Only one thread at a time will be able to acquire a lock on it. TalkativeLocker also has a Func<T> parameter. This parameter represents the procedure to call when the lock is acquired. Developers can use any parameter they wish. I chose Func<T> for simplicity.

The IClient interface parameter represents the class that will be using the TalkativeLocker class. Here is the code for the interface:

C#
public interface IClient
{
    string Name
    {
        get;
        set;
    }
}

The interface currently only has one property: Name. The Name property is used by the TalkativeLocker class to display any information that developers may see fit to make their multi-threading behavior easier while debugging. If there is more than one client thread, the Name property should be set to a unique value: one that will identify the thread to the developer. Notice the TalkativeLocker class using the Name property on the IClient interface to write out logging information to the console.

Here is a simple class that implements the interface:

C#
public class Client : IClient
{
    private string name = String.Empty;
    private readonly TalkativeLocker<int> talkativeLocker = 
                     new TalkativeLocker<int>();

    public Client(string name)
    {
        Name = name;
    }

    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    public int AddTwoNumbers()
    {
        return 1 + 2;
    }

    public void DoSomething()
    {
        int result = talkativeLocker.EnterLock(this, AddTwoNumbers);
    }
}

And a sample class that demonstrates the TalkativeLocker Design Pattern:

C#
static void Main(string[] args)
{
    Client[] clients = new Client[10];

    clients[0] = new Client("1");
    clients[1] = new Client("2");
    clients[2] = new Client("3");
    clients[3] = new Client("4");
    clients[4] = new Client("5");
    clients[5] = new Client("6");
    clients[6] = new Client("7");
    clients[7] = new Client("8");
    clients[8] = new Client("9");
    clients[9] = new Client("10");

    foreach (Client client in clients)
    {
        Thread t = new Thread(client.DoSomething);
        t.Start();
    }

    Thread.CurrentThread.Join();
    Console.ReadLine();
}

Notice that I’ve initialized each client with its own unique ID.

Your output will be the following:

TalkativeLockPattern

I can now see each the status of each thread as they contend for the lock, acquire it, and ultimately release it.

Please let me know if the TalkativeLocker Design Pattern has made debugging your multi-threaded code easier.

License

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


Written By
Software Developer (Senior) Finance Industry
United States United States
Currently pursuing 'Programming Nirvana' (The ineffable ultimate in which one has attained disinterested wisdom and compassion as it relates to programming)

Respected Technologies
1. Confusor (https://confuser.codeplex.com/)
2. Power Threading (http://www.wintellect.com/Resources/visit-the-power-threading-library)
3. EDI Parsers (http://www.rdpcrystal.com)


Acknowledgements:

Microsoft Certified Technologist for WPF and .Net 3.5 (MCTS)
Microsoft Certified Technologist for WCF and .Net 3.5 (MCTS)
Microsoft Certified Application Developer for .Net (MCAD)
Microsoft Certified Systems Engineer (MCSE)
Microsoft Certified Professional (MCP)

Sun Certified Developer for Java 2 Platform (SCD)
Sun Certified Programmer for Java 2 Platform (SCP)
Sun Certified Web Component Developer (SCWCD)

CompTIA A+ Certified Professional

Registered Business School Teacher for Computer Programming and Computer Applications (2004)
(University of the State of New York Education Department)

Graduated from University At Stony Brook

Comments and Discussions

 
GeneralExcellent!!! Pin
AnthonyPaulO23-Dec-09 2:04
AnthonyPaulO23-Dec-09 2:04 
GeneralRe: Excellent!!! Pin
FatCatProgrammer23-Dec-09 6:42
FatCatProgrammer23-Dec-09 6:42 
GeneralRe: Excellent!!! Pin
FatCatProgrammer22-Sep-11 11:08
FatCatProgrammer22-Sep-11 11:08 
C#

namespace ReactiveLogging
{
[Serializable]
public class ErrorSink : IErrorSink, IObservable<List<IErrorInfo>>
{
private int errorBufferLength = 1;
private List<IObserver<List<IErrorInfo>>> observers = new List<IObserver<List<IErrorInfo>>>();
<pre>

private List<IErrorInfo> errors = new List<IErrorInfo>();

/// <summary>
/// Initializes a new instance of the <see cref="ErrorSink"/> class.
/// </summary>
/// <param name="errorBufferLength">Length of the error buffer.</param>
public ErrorSink(int errorBufferLength)
{
Contract.Requires<ArgumentOutOfRangeException>(errorBufferLength > 0);
}

/// <summary>
/// Gets or sets the length of the error buffer.
/// </summary>
/// <value>
/// The length of the error buffer.
/// </value>
public int ErrorBufferLength
{
get
{
return errorBufferLength;
}
set{
errorBufferLength = value;
}
}

/// <summary>
/// Subscribes the specified observer.
/// </summary>
/// <param name="observer">The observer.</param>
/// <returns></returns>
public IDisposable Subscribe(IObserver<List<IErrorInfo>> observer)
{
Contract.Requires<ArgumentOutOfRangeException>(observer != null);

observers.Add(observer);
return new Unsubscriber(observers, observer);
}

/// <summary>
/// Adds the error.
/// </summary>
/// <param name="info">The info.</param>
public void AddError(IErrorInfo info)
{
Contract.Requires<ArgumentOutOfRangeException>(info != null);

//We will take care of possible threading issues here

errors.Add(info);

if (errors.Count > errorBufferLength)
{
NofityAll();
errors.Clear();
}
}

[Serializable]
private class Unsubscriber : IDisposable
{
private List<IObserver<List<IErrorInfo>>> _observers;
private IObserver<List<IErrorInfo>> _observer;

/// <summary>
/// Initializes a new instance of the <see cref="Unsubscriber"/> class.
/// </summary>
/// <param name="observers">The observers.</param>
/// <param name="observer">The observer.</param>
public Unsubscriber(List<IObserver<List<IErrorInfo>>> observers, IObserver<List<IErrorInfo>> observer)
{
this._observers = observers;
this._observer = observer;
}

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (_observer != null && _observers.Contains(_observer))
_observers.Remove(_observer);
}
}

/// <summary>
/// Nofities all.
/// </summary>
private void NofityAll()
{
observers.ForEach(o => o.OnNext(errors));
}
}

[Serializable]
public class ErrorInfo : ReactiveLogging.IErrorInfo
{
private string error = String.Empty;

/// <summary>
/// Initializes a new instance of the <see cref="ErrorInfo"/> class.
/// </summary>
public ErrorInfo()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ErrorInfo"/> class.
/// </summary>
/// <param name="error">The error.</param>
public ErrorInfo(string error)
{
Contract.Requires<ArgumentNullException>(!String.IsNullOrWhiteSpace(error));
Error = error;
}

/// <summary>
/// Gets or sets the error.
/// </summary>
/// <value>
/// The error.
/// </value>
public string Error
{
get
{
return error;
}
set
{
error = value;
}
}
}

/// <summary>
/// Represents a basic error object
/// </summary>
public interface IErrorInfo
{
string Error { get; set; }
}

/// <summary>
/// Represents a basic error sink
/// </summary>
public interface IErrorSink
{
/// <summary>
/// Adds the error.
/// </summary>
/// <param name="info">The info.</param>
void AddError(IErrorInfo info);
}

class Program
{
private static ErrorSink sink;

static void Main(string[] args)
{
List<ErrorInfo> d = new List<ErrorInfo>();

sink = new ErrorSink(5);

sink.Subscribe(e => e.ForEach(o => Console.WriteLine("Heeeeello:" + o.Error)));

BackgroundWorker w = new BackgroundWorker();
w.DoWork += new DoWorkEventHandler(w_DoWork);
w.RunWorkerAsync();

Console.ReadLine();
}

private static void w_DoWork(object sender, DoWorkEventArgs e)
{
for (; ; )
{
IErrorInfo info = new ErrorInfo(DateTime.Now.Millisecond.ToString());
sink.AddError(info);

System.Threading.Thread.Sleep(500);
}
}
}
}
Relativity


modified 22-Sep-11 18:16pm.

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.