I was recently reminded of the UpgradeableReadLock construct C# provides and I'm trying to discern when it really makes sense to use it.
Say, for example, I have a cache of settings that are heavily read by many classes, but periodically need to be updated with a very low frequency based on a set of conditions that aren't necessarily deterministic...
would it make more sense to simply lock like so:
List<Setting> cachedSettings = this.GetCachedSettings( sessionId );
lock(cachedSettings)
{
bool requiresRefresh = cachedSettings.RequiresUpdate();
if(requiresRefresh)
{
// a potentially long operation
UpdateSettings( cachedSettings, sessionId );
}
return cachedSettings;
}
or use an UpgradeableReadLock:
public class SomeRepitory {
private ReaderWriterLockSlim _rw = new ReaderWriterLockSlim();
public List<Setting> GetCachedSettings( string sessionId )
{
_rw.EnterUpgradeableReadLock();
List<Setting> cachedSettings = this.GetCachedSettings( sessionId );
bool requiresRefresh = cachedSettings.RequiresUpdate();
if(requiresRefresh)
{
_rw.EnterWriteLock();
UpdateSettings( cachedSettings, sessionId );
_rw.ExitWriteLock();
}
_rw.ExitUpgradeableReadLock();
return cachedSettings;
}
perhaps what confuses me the most is how we can get away with checking if an update is required outside of the write block. In my example above I am referring to when I check for where a refresh is required, but to simplify I'll use an example from "C# 5.0 In A Nutshell":
while (true)
{
int newNumber = GetRandNum (100);
_rw.EnterUpgradeableReadLock();
if (!_items.Contains (newNumber))
{
_rw.EnterWriteLock();
_items.Add (newNumber);
_rw.ExitWriteLock();
Console.WriteLine ("Thread " + threadID + " added " + newNumber);
}
_rw.ExitUpgradeableReadLock();
Thread.Sleep (100);
}
my understanding is that this allows concurrent reads unless a thread needs to write, but what if two or more threads end up with the same random number and determine !_items.Contains(newNumber)
? Given my understanding that this should allow concurrent reads (and correct me if I have misunderstood, of course).. it seems that, as soon as a write lock is obtained, any threads that were concurrently reading would need to be suspended and forced back to the start of _rw.EnterUpgradeableReadLock();
?