Reputation: 3
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
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
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
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