Xerxex
Xerxex

Reputation: 3

Multi Threading serialization and synchronization

I have the following situation:

A singleton class, let's call it Coordinator which multiple threads are acceding. The role of this class is the "initialize" a certain entity (EntityObject) in a thread safe manner. let's say that we have 5 threads trying to initialize an EntityObject. Only a single thread should be allowed to initialize the EntityObject while the other 4 threads should wait until initialization is done. EntityObjects are unique by their Name.

Here is some code to illustrate this:

public class EntityObject
{
    public EntityObject()
    {
        IsInitialized = false;
        Name = string.Empty;
    }

    public bool IsInitialized { get; set; }

    public string Name { get; set; }
}

public class InitializeArguments
{
    public EntityObject Entity { get; set; }
}

 public class Coordinator
{
    public void initialize(InitializeArguments args)
    {
        if (!args.Entity.IsInitialized)
        {
            //initializeCode goes here
            //only one thread is allowed to initialize an EntityObject with a certain Name
            //the other threads have to wait until initialization is done
            args.Entity.IsInitialized = true;
        }
    }
}


 class Program
{
    static void Main(string[] args)
    {
        List<Task> allTask = new List<Task>();

        Coordinator coordinator = new Coordinator();

        EntityObject entity1 = new EntityObject() { IsInitialized = false, Name = "entity1" };
        EntityObject entity2 = new EntityObject() { IsInitialized = false, Name = "entity2" };
        EntityObject entity3 = new EntityObject() { IsInitialized = false, Name = "entity3" };

        for (int i = 0; i < 4; i++)
        {
            var task = Task.Factory.StartNew(() =>
             {
                 InitializeArguments initArg = new InitializeArguments() { Entity = entity1 };
                 coordinator.initialize(initArg);
             });
            allTask.Add(task);
        }

        for (int i = 0; i < 4; i++)
        {
            var task = Task.Factory.StartNew(() =>
            {
                InitializeArguments initArg = new InitializeArguments() { Entity = entity2 };
                coordinator.initialize(initArg);
            });
            allTask.Add(task);
        }

        for (int i = 0; i < 4; i++)
        {
            var task = Task.Factory.StartNew(() =>
            {
                InitializeArguments initArg = new InitializeArguments() { Entity = entity3 };
                coordinator.initialize(initArg);
            });
            allTask.Add(task);
        }

        Task.WaitAll(allTask.ToArray());
        Console.ReadLine();
    }
}

In this case, entity1,entity2,entity3 should only be initialized once.

I was thinking to use a Dictionary<string, ManualResetEventSlim> to make this happen but i can't make it work.

Upvotes: 0

Views: 1863

Answers (3)

Dustin Kingen
Dustin Kingen

Reputation: 21265

Modifying @KevinHolditch's code to cache the object creation process versus the actual object. Got rid of the Lock.ExitUpgradeableReadLock(); since it was producing an error because there is no read lock. Assuming EntityObject implements IEquatable then there shouldn't be a problem unless reference equality is explicitly required.

public class EntityFactory
{
    private static Dictionary<string, Func<EntityObject>> _entityObjects = new Dictionary<string, Func<EntityObject>>();
    private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();

    public EntityObject CreateEntity(string name)
    {
        Func<EntityObject> result = () => null;

        if (!_entityObjects.TryGetValue(name, out result))
        {
            Lock.EnterWriteLock();
            try
            {
                if (!_entityObjects.TryGetValue(name, out result))
                {
                    result = () =>
                        {
                            // initialisation code here
                            var entity = new EntityObject { Name = name };
                            return entity;
                        };
                    _entityObjects[name] = result;
                }
            }
            finally
            {
                Lock.ExitWriteLock();
            }
        }

        return result();
    }
}

Upvotes: 0

Matten
Matten

Reputation: 17603

Aquire a lock to only allow one thread to enter the critical section:

public class Coordinator
{
    private static object lockObj = new Object();

    public void initialize(InitializeArguments args)
    {
        lock(lockObj)
        {
            if (!args.Entity.IsInitialized)
            {
            ...
            }
        }
    }
}

To take your comment into account: You can lock on the entity itself, but it is considered bad practice (see above link and keep this post in mind):

In general, avoid locking on a public type, or instances beyond your code's control. The common constructs lock (this), lock (typeof (MyType)), and lock ("myLock") violate this guideline

So maybe you're fine with

lock(args.Entity)

instead of lock(lockObj)... But don't aquire a lock on the name of the Entity -- maybe it's unique within your control but who guarantees you that it is unique within the entire process?

Upvotes: 3

Kevin Holditch
Kevin Holditch

Reputation: 5303

How about using a factory and implementing the double locking pattern like so:

public class EntityFactory
{

    private static Dictionary<string, EntityObject> _entityObjects = new Dictionary<string, EntityObject>();
    private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();

    public EntityObject CreateEntity(string name)
    {
        EntityObject result = null;

        try
        {

            if (!_entityObjects.TryGetValue(name, out result))
            {
                Lock.EnterWriteLock();
                try
                {
                    if (!_entityObjects.TryGetValue(name, out result))
                    {
                        // initialisation code here
                        result = new EntityObject() {Name = name};
                        _entityObjects[name] = result;
                    }
                }
                finally
                {
                    Lock.ExitWriteLock();
                }
            }
        }
        finally
        {
            Lock.ExitUpgradeableReadLock();
        }

        return result;

    }
}

The factory should be singleton. It has a static dictionary of entity objects (keyed by name). It checks to see if an entity object exists if it does then it returns it otherwise it obtains a write lock then it checks the dictionary again. As another thread may have created one whilst the write lock was being maintained. If there still isn't one in the dictionary it creates one, replace the code where I have put a comment with your EntityObject initialisation code. Then it stores the entity object in the dictionary and returns it.

Upvotes: 1

Related Questions