Yesterday i discovered we had a multithreading issue with a simple caching object we use:
If Dictionary.Contains(lsKey.ToLower) Then 'if rate cached, then return value
lvResult = Dictionary.Item(lsKey.ToLower)
Else 'else retrieve from database, store, and return value
lvResult = GetRateFromDB(voADO,
veRateType,
vdEffDate)
Dictionary.Add(lsKey.ToLower, lvResult)
End If
We discovered this problem on our asp.net website. The error messaged read something like "your trying to add a value to a hashtable that already exists. As you can tell from the code above the potential definitely exits for this to happen. I was somewhat familiar with waithandles and thought they would solve the problem. So i declared my waithandle at the class level:
private Shared _waitHandle as new AutoResetEvent(True)
Then in the specific section of code with the problem:
_waitHandle.Wait()
If Dictionary.Contains(lsKey.ToLower) Then 'if rate cached, then return value
lvResult = Dictionary.Item(lsKey.ToLower)
Else 'else retrieve from database, store, and return value
lvResult = GetRateFromDB(voADO,
veRateType,
vdEffDate)
Dictionary.Add(lsKey.ToLower, lvResult)
End If
_waitHandle.Set()
for some reason the following code above was ALWAYS blocked. Even the very first thread the accessed the code. I played with things for awhile and even tried to set the waithandle to signaled in the constructor but i never could get it to work.
I end up using the following instead which works fine:
SyncLock loLock
If Dictionary.Contains(lsKey.ToLower) Then 'if rate cached, then return value
lvResult = Dictionary.Item(lsKey.ToLower)
Else 'else retrieve from database, store, and return value
lvResult = GetRateFromDB(voADO,
veRateType,
vdEffDate)
Dictionary.Add(lsKey.ToLower, lvResult)
End If
End SyncLock
So I have two questions:
- Why didn't the waithandle solution work?
- Is SynLock the correct / optimized lock type to use in this case?
1 waithandles block until signaled. You would need something somewhere to signal that waithandle for it to not block the first thread to get access. I believe it would work if you had signaled the handle in the constructor where you created the waithandle. Consider it like there is a slot for a signal inside the waithandle any thread that calls wait will wait until it can consume a signal before leaving the wait call.
2 In this case it probably isn't the best lock to use, if two threads try to read a value that is already in the cache then one will be blocked until the other is finished. I would use a readerwriter lock instead. This way multiple threads can read the cache at the same time and you can upgrade to write when necessary.
If you don't mind multiple loads of the same value to the cache you could use a concurrentdictionary. Any thread that needed a value not yet loaded would load it then call tryadd. In the case where multiple threads try to access the same unloaded value at the same time all of them will do the work of calling GetRateFromDB.